Skip to content
Contact

Case studies / Tigg POS · Jun – Jul 2026

Count first, compare second

A POS session recorded a time and a person, but not money. I designed the cash layer for web and mobile and built the web frontend: an opening count, cash in and out with reasons, a blind closing count, and a required note when the drawer does not balance.

State designFrontendOperationsMerged to production · Jun 2026In use (first-party report)

Role

UI/UX design (web and mobile), web frontend

Timeline

Jun – Jul 2026

Team

CEO, senior frontend engineer, backend engineer, mobile developer, QA

Platform

POS web, POS mobile

Not mine

Backend, the mobile app build

Measured outcome

None measured

the key decision

The cashier counts before seeing the expected amount, so the count is not biased toward the target.

how deep do you want to go?

Design + frontend
Diagram from the case study. Product screenshots are being added.
That's the brief. Switch to Story for the full argument, or Deep for states, iterations and implementation.
Chapter 01

A drawer with no record

A cashier opens a shop's cash drawer in the morning, takes orders all day, pays a supplier for milk out of the till, accepts cash from a regular who is settling last week's credit, and hands the counter over in the evening. At the end of the day the owner has one question: is the right amount of money in the drawer? If not, how much is missing, since when, and why?

Tigg POS could not answer any of it. It already had sessions. Earlier engineers built them in 2023: a person started a session at a location, sold things, and stopped it. But a session recorded a time and a person. It did not record money. There was no opening count and no record of cash taken out or put in. There was no closing count, and nothing to compare one against. When the drawer came up short, the difference had no owner, no timestamp and no explanation. In my own words from the time, shops had no record.

2023 · a stopwatch with a name 2026 · a drawer with a ledger Counter 1 (In Progress) Overview Opened ByOpening TimeClosing TimeRun Time Sales Notes Counter 1 (In Progress) Close Session Overview Transaction Summary SalesRefundsCashQR / walletCard Cash Drawer Opening BalanceCash SalesRefundsCredit CollectionCash InCash Out Expected CashClosing BalanceDifference Notes
Reconstruction, fictional dataThe session detail before and after. The 2023 page showed an overview, sales and notes. The 2026 page keeps the overview and notes, moves payments into a Transaction Summary, and adds a Cash Drawer card that reads like a ledger. Redrawn from the shipped screens with placeholder text.

The job was to turn a stopwatch with a name on it into an accountable cash drawer: every session carries an opening count, cash in and out with reasons, a closing count checked against what should be there, and an explanation whenever the two don't match. And shops that don't want any of this shouldn't be slowed down by it.

Chapter 02

My part, and the team's

As I remember it, the request came from sales and went to the CEO. The CEO planned the feature and asked me to ideate and design it. I am the only UI/UX designer at Tigg, and I usually work with the CEO and a senior frontend engineer, who both sometimes review my Figma files.

AreaWho
Plan and priorityCEO
UX and UI, web and mobileMe
Web frontend (the POS web app)Me
Backend: storage, the expected-cash calculation, historyBackend engineer
Mobile app buildMobile developer (I designed the screens; I didn't build the app)
Review and merge of my frontend workSenior frontend engineer
TestingQA

The work followed Tigg's usual path from ticket and design through a "Ready for dev" handoff, development, QA and UAT to a production release. My first working version ran on 2 June 2026. The feature reached the production branch in the second half of June, and I kept refining it into mid-July.

Chapter 03

What I knew, and how I knew it

I want to be precise here, because this part is thin.

  • The brief. The CEO's plan, and conversations with him while I ideated. There was no written requirements document.
  • Other products. I looked at the cash-session features of many other POS apps. I didn't write down which ones, so I won't name them as things I studied. Zoho is the product I usually look to for patterns.
  • An internal demonstration with real equipment. We walked through the flow in the office with actual cash drawers and the counter hardware. It was a hardware walkthrough, not user research: nobody outside the team took part.
  • Indirect signal. I hear about user problems from QA, support and the CEO. During the build, QA's follow-up list was the most concrete feedback I had.

There were no shop visits, interviews, usability tests or analytics. The design rests on the CEO's knowledge of the shops, the patterns other POS products share, and careful reasoning about money: what a number means, what "nothing" means, and who may change what.

Chapter 04

The drawer as a ledger

Before drawing screens I wrote down how money moves through one drawer in one shift. Expected cash isn't a number the system should simply assert; it's a sum a manager should be able to re-add from top to bottom:

Expected cash = opening count + cash sales − cash refunds + credit collected in cash + cash in − cash out

Difference = counted cash − expected cash

05,00010,00015,00020,000 NPR (fictional session) 5,000+12,400−600+1,500+500−1,200 17,60017,300 OpeningCash salesRefundsCredit coll.Cash inCash outExpectedCounted difference −300 (short) 1.7% of expected: too small to see, so the UI names it total adds cash removes cash physically counted
ChartOne fictional session as a waterfall. The movements add up to an expected Rs 17,600; the cashier counts Rs 17,300. A Rs 300 gap is under 2% of the total and invisible in a chart, so the interface names it in words: short by Rs 300. Before this work, only the cash-sales bar existed.

Writing it as a ledger exposed three gaps a sales report hides:

  • Cash that moves without a sale. Change top-ups, a supplier paid from the till, an owner taking money out. Without a record of these, no expected figure can be honest. So cash in and cash out became their own entries, each with a reason.
  • Credit settled in cash. In Nepali shops customers often run a tab and pay it later. That cash is real, but it isn't a new sale. It needed its own row, and the backend engineer made sure credit collections reached the session report.
  • Cash versus everything else. Shops take QR and wallet payments alongside cash. The drawer should only count what is physically in it, so the Cash Drawer card uses the cash share of sales, refunds and collections, and the other payment methods stay in the Transaction Summary.

Counting also had to fit Nepali money. The default denomination list is Rs 1,000, 500, 100, 50, 20, 10, 5, 2 and 1, in descending order, which is how a cashier counts. The less common Rs 25 and Rs 250 notes are not in the default, but a shop can add them in settings. That is why the list is editable rather than fixed.

Chapter 05

Three things that change on their own

The first thing I had to get right was not a screen but a model. A session in this feature varies along three separate dimensions:

  1. Lifecycle: in progress, or closed.
  2. Cash result: balanced, short or excess. It exists only for closed sessions at locations that verify cash.
  3. Who is looking: the session's owner, an admin, or another member of staff.
1 · Lifecycle In Progress Closed Every session, both modes 2 · Cash result Balanced Short ↓ Excess ↑ Closed sessions, strict mode only 3 · Actor Owner · can act Admin · can act on any Other · view only Decided per viewer, per session ×× In the list filter: Short → closed, and the drawer came up short The Balanced / Short / Excess options appear nested under Closed, and only when the location is strict.
DiagramThree dimensions that vary independently. Keeping them apart is what makes 'show me every short session last week' a one-click question in the session list.

It's tempting to merge the first two, so that "Closed" implies everything is fine. But a closed session is not necessarily a balanced one, and an owner reviewing last week wants exactly the closed sessions that came up short. So the list filter nests Balanced, Short and Excess under Closed, and only shows them when the location verifies cash. The third dimension decides what each person can do: the owner of a session and admins can record and correct cash; everyone else sees the record but can't change someone else's drawer.

With the dimensions separated, the states followed. Cash verification is a per-location setting called Require Cash Verification, a label the CEO chose. When it's on (internally we called this strict mode), the session has to be opened with a count and closed with one. When it's off (easy mode), start and close are one click each, and counts can still be added later if a shop wants them.

No session In progressneeds opening In progressopening recorded Closingcount (blind) Closingfetching expected Closingfetch error Reviewbalanced Reviewshort or over Closedshort Closedover Closedbalanced Closedno closing count start + count easy: 1 click started without count Enter OpeningBalance cash in/out, edits Close (strict) Submit Count error retry Back with note note optional Close (easy) + Add Closing Balance (later)→ Closed (any result) strict path easy or recovery path both modes Empty note on short/over:stays in Review, error + scroll.
DiagramThe session and cash state machine. The blind count, the separate 'fetching expected' state with its own error and retry, and the note rule on the imbalanced branch are all explicit. The dashed path is easy mode, or recovery when a session exists without an opening count.
Chapter 06

Count first, compare second

The closing flow is the heart of the feature, and the most important decision in it was deliberate from the start: the cashier counts before seeing the expected amount.

If the expected figure sits next to the input, the count is no longer independent. A tired cashier who counts Rs 17,280 and sees Rs 17,600 on screen has every reason to recount until the number agrees, or just type the target. I wanted to reduce that bias toward matching the expected amount. So closing has two phases. In the first, the cashier enters a total or counts by denomination, and nothing on screen says what the answer "should" be. Only after Submit Count does the system fetch the expected cash and show the review: expected, counted, the difference, and a plain-language status.

Close Session — Count Cash AmountDenomination Counted Cash 17,300 No note field here, and no expected amount: the count is made before the target is seen. Couldn't fetch the expected cash.Please try again. (error state, shown only on failure) Cancel Submit Count Close Session — Review Balanced Counted cash matchesthe expected amount. Expected17,600 Counted17,600 Difference0 Note (optional) Back Close Session Close Session — Review Short Drawer is short byRs 300. Expected17,600 Counted17,300 Difference−300 Note (required) A note is required whenthe drawer is not balanced. Back Close Session Close Session — Review Over Drawer is over byRs 200. Expected17,600 Counted17,800 Difference+200 Note (required) A note is required whenthe drawer is not balanced. Back Close Session
Reconstruction, fictional dataThe two-phase close. The count phase has no expected figure and no note field. After Submit Count, the review shows one of three results; when the drawer is not balanced, the note becomes required. Redrawn from the shipped modal; fictional amounts.
Figma designThe same flow in the design file.

Here is one fictional session from its opening count to a close that comes up short. Step through it; every step is also listed below the panel.

One session, from opening count to a short close

Step 1 / 8

No session

The cashier starts a session

At a location that verifies cash, starting a session opens the opening-count modal instead of starting straight away.

Require Cash Verification
On
Opening cash
Not entered yet
Interactive reconstruction, fictional dataFictional NPR amounts. The location has Require Cash Verification switched on.
All steps as a table
#StateWhat happens
1No sessionThe cashier starts a session. At a location that verifies cash, starting a session opens the opening-count modal instead of starting straight away.
2OpenCount what is already in the drawer. The cashier counts by denomination. The total updates as each row changes. Zero would be a valid answer here; a blank field would not.
3OpenMoney moves without a sale. The owner adds change for the day, and the cashier pays a supplier from the till. Each movement needs an amount above zero and a note.
4OpenSales, a refund and a credit payment. Only the cash share counts toward the drawer. QR and wallet payments appear in the Transaction Summary, not here.
5Closing · CountCount the drawer without a target. Closing starts with a count. The expected amount is not on screen, and there is no note field yet.
6Closing · ReviewThe comparison appears. After Submit Count, the system fetches the expected amount and names the result in words, not only in colour.
7Review · ShortAn empty note blocks the close. The note label changes from optional to required. Pressing Close Session with it empty shows 'A note is required when the drawer is not balanced.' and scrolls to the field.
8Closed · ShortA difference with an owner, a time and a reason. The session closes. The drawer card and the session list now show it as short, with the amount and the explanation beside it.

The blind count has one honest limit. During an open session, the Cash Drawer card on the session detail does show the running expected cash, because people need to see the drawer build up. A cashier who wants the target can find it. The two-phase close removes the target from the moment of counting; it doesn't make the whole product blind.

Chapter 07

Empty is not zero, and a mismatch needs a reason

Two small rules carry most of the feature's integrity.

Empty means forgotten; zero means no cash

A blank opening count and an opening count of zero look alike on a screen, but they mean different things. Blank means someone forgot to enter it: the value is unknown. Zero means someone checked and the drawer really was empty: the value is known. If the interface quietly turned a blank into 0, a forgotten count would look like an honest empty drawer, and every difference after it would be wrong.

So a balance field keeps a typed 0 visible and accepts it, and a blank field is rejected with "Enter an opening amount." A cash movement is different: it is an event, and a movement of zero didn't happen, so cash in and cash out need an amount above zero.

Opening: blankOpening: 0Opening: 500Cash Out: 0 0.00 0 500 0 Blocked · unknownToast "Enter anopening amount." Accepted · knownThe drawer reallystarted empty Accepted · knownNormal float Blocked · meaningless"Enter an amountgreater than zero." A balance is a statement of fact, so 0 is a valid fact. A movement is an event, so a movement of 0 did not happen. The same rule holds in denomination mode: all-zero counts are a valid balance (sums to 0) but an invalid movement.
Reconstruction, fictional dataFour input states and their outcomes. The status is carried by the words ('Blocked', 'Accepted'), not by colour alone. Fictional data; the rules and messages are the shipped ones.

A note is required only when something is wrong

When the drawer is balanced, the closing note is optional. When it's short or over, the note is required, because a mismatch means something went wrong and the record should say what. The same logic applies wherever money changes without a sale: every cash in and cash out needs a note, and so does every edit to an opening or closing balance.

I didn't make the note required on every close. A note that is always mandatory soon becomes "ok" typed forty times a week, and then it carries no information. Asking only when the numbers disagree keeps the request rare, and so it is more likely to get a real answer.

Chapter 08

Put the interruption where the money moves

At a location that verifies cash, what happens if a cashier tries to sell before entering an opening count? Something has to stop them. The question was where.

My first version of the check, on 9 June, sat at every entry point: the restaurant order page, the retail order page, quick orders and the payment calculator. It worked, but it could stop a cashier while they were still building an order, with a customer waiting, and it had to be kept consistent across four places. On 18 June I moved it to one place: the payment page. The page asks for the opening count when it loads, and again if the cashier presses Complete Order without one. I made the change for ease of use and a better experience at the counter. Order-building is never interrupted, and cash is asked about at the moment cash is about to be taken.

Before · 9 Jun After · 18 Jun Restaurant order Retail order Quick order Payment calculator G G G G Payment pagecomplete order 4 enforcement points; the cashier is stopped while building an order. Restaurant order Retail order Quick order Payment page G · prompt on load G · on Complete Order 1 place, 2 triggers. Order-building is never interrupted; the pending action resumes after the count is saved.
DiagramGate placement before and after the 18 June change. Four enforcement points became one place with two triggers.

Moving the gate also meant the interrupted action had to survive the interruption. If a cashier presses Complete Order and is asked for the opening count first, saving that count should finish the order, not dump them back to press the button again. And cancelling the prompt should take them back to the order without a confusing "unsaved changes" warning. Both are small, but they decide whether the gate feels like a checkpoint or a trap. Chapter 14 describes a timing bug this exposed.

Chapter 09

What I took out

Most of the decisive changes in this project removed something. I built each of these, used it, and replaced it.

WHENTRIEDREPLACED BY 2 → 8 Jun2 → 8 Jun2 → 8 Jun9 → 18 Jun23 → 24 Jun Config in browser storageMode and denominations kept in the browser,so the flow could run before the API existed Drag-to-reorder + enable checkboxesEach denomination could be dragged,edited, enabled or disabled Separate Opening and Closing cardsTwo cards on the session detail, eachwith its own count Gate at "Proceed to payment"On restaurant, retail and quick-order pages,plus the payment calculator "Enable Cash Denominations" toggleA second per-location switch, storedin one browser only Server settings and denomination APIThe prototype let me test the whole flow a weekbefore the API landed; then it moved to the server Edit / delete list, auto-sorted by valueOrder carries no meaning for cash; a disabled-but-present note was two concepts for one outcome One Cash Drawer card, in ledger orderOpening → sales → refunds → credit → in → out →expected → closing → difference: re-addable One gate on the payment pageEasier for the cashier: order-buildingstays uninterrupted Removed the next dayNot necessary: denomination mode is there wheneverthe location has denominations configured
DiagramFive things that were built, tried and replaced, with dates. The reasons given for the gate move and the toggle removal are my own; the others are how I read the changes in retrospect.
  1. 2 Jun 2026A working prototype before the APIThe whole open, count and close flow ran in the browser, with settings kept locally, so the team could walk through it before the backend was ready.
  2. 8 Jun 2026Connected to the real APITwo separate Opening and Closing cards became one Cash Drawer card in ledger order. Drag-to-reorder and per-denomination checkboxes became a plain list sorted by value.
  3. 9 – 16 Jun 2026HardeningNotes required on cash movements, zero allowed on balances, history per movement, loading and error states, and fixes for bugs found in testing.
  4. 18 Jun 2026One gate, on the payment pageThe check moved from four order entry points to the payment page, for ease of use.
  5. 23 – 24 Jun 2026A second switch, removedAn 'Enable Cash Denominations' toggle was added and removed the next day as not necessary: denomination counting is available whenever the location has denominations configured.
  6. Jul 2026RefinementSettings are loaded only when someone is about to start a session, rather than for every location on screen.

The single ledger card was the change I'm happiest with in retrospect. Two cards, one per count, gave you two numbers and no explanation. One card that lists opening, cash sales, refunds, credit collection, cash in, cash out, expected, closing and difference, in that order, lets a manager re-add the column and see where a gap came from.

The settings label changed too. It began as "Strict Session Cash", became "Session Cash Management", and ended as Require Cash Verification, the CEO's choice, with the helper text "Require users to record opening and closing counted cash for each session." The final version names what staff will have to do, not the mode's internal name.

Chapter 10

What shipped, and what isn't measured

Shipped: opening counts by total or by denomination; cash in and out with notes; the blind two-phase close; edits with a visible history; the Cash Drawer card; filtering sessions by cash result; the per-location setting with an editable denomination list; the payment-page gate; and an "Open 12+ hours" label for sessions left open. I designed web and mobile and built the web frontend; the mobile developer built the app.

StatusDetail
Merged to productionJune 2026, after QA and UAT
In useReported to me by the CEO
Measured outcomeNone
Live and properly used by real shops.Reported to me by the CEO

That report is my only evidence of use. I don't know how many locations switched verification on, how often drawers close short, or whether closing got faster or slower.

If I could instrument one thing, it would be the share of verified sessions that close with a count, broken down into balanced, short and excess, alongside how many imbalanced closes carry a note that says something. Together those tell you whether the loop works: people count, differences are visible, and differences are explained. I'd also watch the time between the payment page opening and the opening count being saved. A gate that slows the counter too much is the most likely reason an owner would switch verification off, and that would undo the whole feature.

Chapter 11

What I learned

  1. 01

    Model the money before the screens

    The feature became manageable once opening, movement, closing and difference were separate records. After that, every screen was a view of the same ledger, and the ledger order became the card layout.

  2. 02

    Nothing has two meanings

    In anything financial, blank means unknown and zero means known and empty. Keeping them apart is a data-integrity decision, not a detail of the input field.

  3. 03

    Interrupt where the consequence is

    The gate got simpler and less disruptive when it moved to the one place where cash is actually taken. Where you ask matters as much as what you ask.

The hardest part wasn't any single screen. It was timing: what the interface shows while the server is still catching up, and whether an interrupted action survives the interruption. Most of what I fixed after the first build was about that, not about how things looked. Designing this flow meant designing its timing too. Next time I'd draw those in-between states in the first sketch rather than discovering them in testing.

Deep dive 12

States and edge cases

The state machine in chapter 05 is the skeleton. This is the catalogue of situations each screen has to survive, with what the product does in each. Messages are quoted as they appear in the interface.

Input

SituationBehaviour
Opening count left blankBlocked. "Enter an opening amount." Nothing is saved.
Opening count of 0Accepted. The 0 stays visible in the field, so the cashier can see what they confirmed.
Closing count left blankBlocked. "Enter a counted amount." The expected amount is not requested.
Cash in or out of 0, or blankBlocked. "Enter an amount greater than zero."
Cash in or out without a noteBlocked. An inline "Note is required." and the form scrolls to the note.
Denomination mode, every count at 0A valid balance (it sums to zero), but not a valid movement.
Location has no denominations configuredThe Amount / Denomination switch is hidden and the form counts a total.
Editing an old entry whose denomination was later removed from settingsThe form shows both the current list and the denominations the entry used, so nothing silently disappears from a historical count.

Network and timing

SituationBehaviour
Expected cash fails to load during closeThe cashier stays on the count, sees "Couldn't fetch the expected cash. Please try again." and can submit again without leaving the modal.
Some cash drawer details fail to load"Couldn't load some cash drawer details." with Retry. Only the parts that failed are fetched again, so one slow request doesn't blank the whole card.
History fails to load, or is empty"Unable to load history", or "No history yet".
Session just started, but the session data hasn't refreshedThe opening prompt doesn't reappear. See chapter 14.
SavingButtons are disabled while a save or fetch is in flight, and a progress toast says what is happening: "Starting session...", "Closing session...", "Saving...".

Missing or partial records

SituationBehaviour
A session exists but has no opening count (started in easy mode, or before verification was switched on)At a verifying location, the drawer card shows a warning: "Session has not started yet. You need to enter opening balance to start the session." with a button to enter it. The payment page asks for it too.
A balance known only from the session summary, with no editable entry behind itShown, but not editable: "This opening balance cannot be edited." A display value should never pretend to be a record you can change.
Session closed in easy mode, with no closing countThe owner or an admin can add a closing balance later from the drawer card, and the difference is then computed as usual.
Verification switched off while the list is filtered by ShortThe cash-result filter is removed, because those options no longer exist for that location.
Session open for 12 hours or moreAn "Open 12+ hours" label on the location card on the home screen, a nudge to close it.

Denied

People who don't own a session and aren't admins see the drawer and its history, but the add, edit, delete and Close Session controls are not shown to them. Hiding rather than disabling keeps the card readable for the common case, a cashier looking at their own drawer. The cost is that a colleague looking at someone else's session gets no explanation of why they can't act. A short line such as "Only the session owner or an admin can change this drawer" would fix that, and it's one of the first things I'd add.

Deep dive 13

Corrections, history and who can change what

A cash record that can't be corrected gets worked around; one that can be corrected silently can't be trusted. Cashiers mistype. An owner who spots that the morning count was entered as Rs 50,000 instead of Rs 5,000 needs to fix it, and anyone reading the session later needs to see that it was fixed, by whom, and why.

So every cash entry (opening balance, closing balance, each cash in, each cash out) keeps its own history. Creating, editing and deleting each leave an entry with the person and the time. Edits show before and after for each field that changed: the amount, the note and the individual denomination counts. Fields that didn't change aren't repeated. An edit requires a note, for the same reason an imbalanced close does: a correction should explain itself.

Cash In History DeletedAdmin · 12 Jun 2026, 21:44 Amount 4,500"Owner top-up for change" EditedAdmin · 12 Jun 2026, 21:40 Amount5,000→4,500 Note"Change for the day" → "Owner top-up for change" Rs 500× 10→× 9 CreatedCashier · 12 Jun 2026, 10:15 Amount 5,000"Change for the day" Newest first. Times in Nepal time (+05:45), 24-hour. Every action is a record. Create, edit and delete each leave an entry with who and when. Edits show before → after. Amount, note and denomination counts are diffed field by field; unchanged fields are not repeated. An edit requires a note. So a correction always explains itself, like an imbalanced close. Status is a word, not a colour. "Created / Edited / Deleted" carry the meaning; the dot only echoes it.
Reconstruction, fictional dataThe history of one cash movement, newest first. The admin corrects an amount and its note, then deletes the entry; the cashier's original record stays visible underneath. Fictional names and amounts; times shown in Nepal time, 24-hour.

One detail in the history view is specific to Nepal. Nepal is five hours and forty-five minutes ahead of UTC, and timestamps came back with that offset written in more than one form. The history reads both forms, because a quarter-hour slip is enough to make a sequence of corrections look out of order. In June the session times also moved to a 24-hour clock.

Who can do what

ActionSession ownerAdminOther staff
See the drawer and its historyYesYesYes
Add a missing opening balanceYesYesNo
Edit an opening or closing balance (note required)YesYesNo
Add, edit or delete cash in and cash outYesYesNo
Close the sessionYesYesNo
Add a closing balance after an easy-mode closeYesYesNo

The frontend shows or hides these actions by role. The backend enforces its own rules, which I didn't design.

Deep dive 14

Building the web frontend

I built the web side of this myself, in the POS web app used on counter desktops and tablets. Being both designer and frontend developer meant I could test a decision in the real product within hours, and it also meant some design problems only appeared once the thing was running. This chapter describes behaviour and tradeoffs in plain language; the code stays with Tigg.

A prototype before the API

The backend and the frontend were being built at the same time, and the field names were still changing. So my first version, on 2 June, ran the whole flow in the browser: the verification setting, the denomination list, the opening modal and the two-phase close. It let me run opening, counting and closing end to end almost a week before the real API was ready, and it's where the blind count and the note-on-mismatch rule first ran. On 8 June I connected it to the backend. I kept a single translation layer between the interface's idea of a count (a mode, an amount, the denominations and a note) and what the server expected, so later renames on the backend touched one place instead of every screen.

The server decides what "expected" is

The expected cash is computed by the backend, not in the browser. The close review shows the server's figure and the server's difference. Doing the sum in two places would eventually produce two answers, and in a cash feature two answers are worse than none. The frontend's job is to show the parts of the sum (the rows of the Cash Drawer card) so the total can be checked by eye.

After every change, the whole drawer is refreshed

Adding a cash out changes the movement list, the expected cash, the session summary and the history at once. After any save, edit or delete, the frontend refreshes all of those together. It costs a few extra requests. The alternative, a card where one row has updated and another hasn't, would make a cashier doubt the numbers in exactly the moment they're checking them.

The prompt that came back

Moving the gate to the payment page exposed a timing problem. The cashier saved an opening count, the server started the session, but the page's copy of "does this cashier have a session?" hadn't refreshed yet. For a moment the page still believed there was no session, so it asked for the opening count again. A cashier who had just counted the drawer was asked to count it again.

NaiveImplemented CashierPayment page + gateServer CashierPayment page + gateServer Complete Order opening modal Save opening start session ok (data not refreshed yet) still "no session"→ modal opens again Cashier is askedto count again. Complete Order store pending action opening modal Save opening start session ok suppress prompt 4 s refresh session data close modal run pending action order completes once Auto-prompt also waits while any fetch is in flight.
DiagramThe naive and the implemented gate. In the naive version, stale session data re-opens the modal. The implemented version remembers the action that was interrupted, holds the prompt back briefly while session data refreshes, and then completes the order once.

The fix had three parts. The gate remembers what the cashier was trying to do, so saving the count completes the order. After a successful start, it holds the prompt back for a few seconds while the session data refreshes. And the automatic prompt waits whenever session data is still loading, rather than deciding on old information. The short hold was added first, as a pragmatic fix; the "wait while loading" rule came later and is the more principled one. Both are in place now. If I revisited this, I would rely on the loading rule alone and remove the timer.

Two related fixes came out of the same area. The success handler had been calling the same "close" path as cancelling, which on the payment page meant navigating back; saving now only resets the form and lets the page decide. And cancelling the prompt used to trigger the page's "unsaved changes" warning, which made no sense to a cashier who hadn't changed anything; cancel now goes straight back to the order.

A form that forgot what you typed

One bug I fixed in mid-June: in the edit modal for a cash movement, the form reset itself on every screen update, so typing into it could be lost. The form now resets only when it's first opened or when a different entry is chosen. It's the kind of fault that's invisible in a design file and very visible at a counter.

Loading settings only when needed

The home screen lists every location, and each location card needs to know whether that location verifies cash, but only when someone starts a session there. In July I changed it so that setting is loaded on intent (when someone presses start, or opens the menu that ends a session) instead of for every card on page load. Nothing looks different, but the home screen does less work.

Things I'd tidy

  • Some early components from the two-card layout were left in place after the ledger card replaced them. They're unused and should go.
  • The frontend reads several spellings of some fields from the server because the API was changing during the build. That tolerance kept the screens working, but it's complexity that should shrink now the API has settled.
  • There are no feature-specific automated tests. Testing was manual, through QA and UAT. The count, review and note rules are the first place I'd add them.
Deep dive 15

The surfaces, on web and mobile

The feature touches more places than the close modal. I designed the web and mobile screens on one Figma page, "🟢 Session Management" in the "Tigg New Features" file, which carries the team's "Ready for dev" marker. The mobile developer built the app from it; I built the web.

Session list

The list shows who opened each session, when it opened and closed, and its status. The status filter has In Progress and Closed, and, at locations that verify cash, Balanced, Short and Excess nested under Closed. Result chips carry an arrow and an amount as well as a colour, so a short session reads as "short, down Rs 300" without relying on red. To make the nesting possible I extended the shared table filter used across the POS with conditional nested options, so other lists can use the same pattern.

Figma designThe session list, filtered by cash result.

Session detail

The detail page is a two-column layout: Overview across the top (opened by, opening and closing time, run time), then Transaction Summary and Cash Drawer side by side, then Notes. The Cash Drawer card is the ledger from chapter 04, with rows that expand to show individual movements and their History, Edit and Delete actions. Add actions sit at the foot of each section ("+ Add Cash In", "- Add Cash Out"), close to the numbers they change. Cash in is shown in green and cash out and refunds in red with a minus sign, and each row is also labelled in words.

Figma designThe session detail, with the drawer read top to bottom as a ledger.

Opening and closing modals

The opening modal has an Amount / Denomination switch. In denomination mode each note has a stepper with minus and plus buttons as well as a typed count, and a live total. The same modal is reused when a session exists without an opening count; it then carries a warning and its button reads "Save Opening Balance" instead of "Start Session". The close modal has the two phases from chapter 06.

Figma designOpening and closing, as designed.

Mobile

On mobile the session detail becomes a bottom sheet over the session list, designed on iPhone 14 frames at 390 points wide. I designed these screens; I didn't build the app, and I haven't compared the built app against the design in detail.

Figma designMobile: the session list and the detail bottom sheet.

Settings

Require Cash Verification sits in the location's general settings. When it's on, the denomination editor appears below it: edit a value, delete one, add one (the placeholder suggests "e.g. 25"), reset to the default list, save. The settings live on the location, on the server, so every device at the same counter agrees. That is also why a second, browser-only switch for denominations didn't belong (chapter 09).

Figma designVerification is a location policy, with the denomination list under it.

Against other products

For this write-up (September 2026, not during the project) I read the public documentation of a few POS and ERP products to place the design. It is a documentation scan, not hands-on testing.

CapabilitySquareOdoo 13ERPNextTigg
Opening countStarting cashOpening countOpening entryTotal or by denomination; zero allowed, blank rejected
Cash in and outPay in / out, description requiredCash in / outNot notedCash in / out, amount above zero and note required
Close against expectedActual amount at endTheoretical against countedClosing entryCount first, then review expected, counted and difference
Reason on a mismatchOptionalNot notedNot notedRequired when short or over
Correction historyPaid in / out historyNot notedNot notedPer entry: created, edited, deleted, with before and after

The baseline (opening count, cash in and out, counted against expected) is common to all of them, so matching it is table stakes, not a contribution. Where Tigg differs is the accountability layer: the reason required on a mismatch, the per-entry history, and the separation of lifecycle from cash result in the list.

Deep dive 16

What I'd change, how I'd test it, and sources

What I'd change

Nobody asked me to defend these, so this is my own judgement, grounded in how the feature behaves today.

  1. Watch real closes first. The biggest gap is research. Before any further change I'd sit with two or three cashiers at shift change, at one verifying shop and one that isn't, and note where they hesitate, what they write in the note, and whether the gate ever gets in the way of a sale.
  2. An optional blind mode for the whole session. The close is blind; the session detail isn't. Some shops would want cashiers not to see the running expected cash at all. It should be a choice for the owner, because other shops want cashiers to see the drawer build up.
  3. Say why an action is missing. One line for staff who can see a drawer but can't change it.
  4. A printed session report. Shops that close on paper would want the drawer summary on the receipt printer.

How I'd test it

Task-based sessions with cashiers. Five tasks, each tied to a risk in the design: start a session with an empty drawer (does zero feel allowed?); record a supplier payment from the till (is the note natural or a nuisance?); close a drawer that's Rs 300 short (does the blind count feel fair, and what do people write?); correct a mistyped opening count as an admin (is the history understood?); and recover when the expected cash fails to load. Five cashiers from two or three shops would be enough to find the large problems.

Instrumentation. The measures I'd add, in order:

QuestionMeasure
Do people count?Share of verifying locations' sessions closed with a count
Are differences visible?Balanced, short and excess per week, and the typical size of a difference
Are they explained?Share of imbalanced closes whose note is more than a word or two
Are corrections honest?Edits per session, and edits made after close
Is the gate costly?Time from the payment page opening to the opening count being saved
Is anything left open?Sessions flagged "Open 12+ hours" per week

The last two are guard rails. If the gate slows the counter, owners will switch verification off, and the feature quietly stops existing.

Accessibility checks. Focus order through the two phases of the close; whether a screen reader announces the change from count to review; and a text label behind every arrow chip in the list. I relied on words rather than colour throughout, but I haven't tested with assistive technology.

Notes on sources

  • First-party: my own recollection and answers written for this case in September 2026: the trigger, the lack of any record before, the internal demonstration with cash drawers, the meaning of empty and zero, the reasons for the note rule, the blind count, the gate move and the toggle removal, and who chose the settings label.
  • Product behaviour: the shipped web product and my own implementation. Messages in quotation marks are the interface's own copy.
  • Design: the "🟢 Session Management" page in the "Tigg New Features" Figma file.
  • Dates: my work history for the feature, June and July 2026.
  • Feedback: the CEO's report that the feature is live and properly used by real shops. There is no usage data behind it that I can see.
  • External: public documentation from Square, Odoo and ERPNext, read in September 2026; the denomination list against the notes and coins in circulation from Nepal Rastra Bank's public material.
  • Reconstructions: every redrawn screen and diagram on this page uses fictional names and amounts. Where I give a reason that I didn't record at the time, I say it's in retrospect.
Deep chapters (states, iterations, implementation) are hidden in Story mode. Switch to Deep at the top to read them.