# Bibhushan Saakha > Bibhushan Saakha is a UI/UX designer and frontend engineer based in Kathmandu, Nepal. Since April 2025 Bibhushan has been the only product designer on Tigg, BIC Technology's point-of-sale (POS) and accounting software for Nepali businesses, and one of its frontend engineers. The work covers operational software: cash sessions, batch and serial tracking, bulk import, multi-currency, sign-in and backup flows. Bibhushan studied Computer Engineering at Kathmandu University (2020–2025); founded Myra, a women's-health concept that placed 2nd Runner-Up at the Hult Prize at Kathmandu University 2025 and won first prize at Project YuwaXcel 2026; and holds Nepal's national record in 4×4 blindfolded Rubik's Cube solving (WCA, 2019). Site: https://www.bibhushansaakha.com.np · Email: bibhushansaakha@gmail.com · LinkedIn: https://www.linkedin.com/in/bibhushansaakha · GitHub: https://github.com/bibhushansaakha Key facts: - Who is Bibhushan Saakha? Bibhushan Saakha is a UI/UX designer and frontend engineer in Kathmandu, Nepal. Since April 2025 Bibhushan has been the only product designer on Tigg, BIC Technology's point-of-sale and accounting software for Nepali businesses, and one of its frontend engineers. - What does Bibhushan Saakha design? Operational business software: POS cash sessions, batch and serial tracking, bulk data import, multi-currency and bank reconciliation, sign-in, backups, admin tooling and settings information architecture, plus research-led concepts such as Myra, a women's-health app concept. - Is Bibhushan a designer or a developer? Both. Bibhushan designs in Figma and also builds frontend in React and Next.js, so many features go from research and design to shipped web frontend with one person carrying the intent through. - Where did Bibhushan Saakha study? Kathmandu University, B.E. in Computer Engineering, December 2020 to August 2025. - What awards has Bibhushan Saakha won? With Myra: 2nd Runner-Up at the Hult Prize at Kathmandu University 2025 and first prize at Project YuwaXcel 2026. Separately, Nepal's national record in 4×4 blindfolded Rubik's Cube solving at the Nepali Nationals 2019 (World Cube Association). - Is Bibhushan Saakha open to work or collaboration? Yes. Bibhushan is open to conversations about product design, design engineering and frontend roles, and is preparing graduate applications in HCI and digital health. Contact: bibhushansaakha@gmail.com. ## Case studies - [Count first, compare second (Tigg POS)](https://www.bibhushansaakha.com.np/work/cash-sessions): 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. Role: UI/UX design (web and mobile), web frontend. Status: Merged to production (Jun 2026); In use (first-party report). Measured outcome: None measured. - [Repair supported import errors in Tigg, before posting (Tigg Accounting)](https://www.bibhushansaakha.com.np/work/bulk-import): Tigg's old importer listed errors by row number and offered one way forward: fix the file in Excel and upload it again. I designed a replacement where people map their own columns with the gaps shown, then fix, filter and bulk-edit rows in a grid before anything is posted. Role: UI/UX design only (sole designer): research, flows, every state in Figma. Status: Design handoff (Before Jun 2026); Tagged release (Jul – Aug 2026); Branch only (Sep 2026); In use (first-party report). Measured outcome: None measured. - [A tracked item cannot enter the cart incomplete (Tigg POS)](https://www.bibhushansaakha.com.np/work/batch-serial): Businesses that track batches, expiry or serial numbers need that identity to survive every step. I designed batch and serial tracking across eight surfaces and built the POS frontend, prototyping the flows directly in code. Role: UI/UX design across 8 surfaces, POS frontend. Status: Tagged release (Sep 2026); In use (first-party report). Measured outcome: None measured. - [She controls what they see (Myra)](https://www.bibhushansaakha.com.np/work/myra): Myra was a women's-health concept for Nepal. I led it as founder and designer. A 171-response, two-sided survey moved it from a broad AI-health pitch to one testable question: consent-first sharing with a partner or family member. 3rd at Hult Prize at Kathmandu University 2025; first prize at Project YuwaXcel 2026. Role: Founder, UI/UX design, brand and marketing, web frontend. Status: Concept, paused (2026). Measured outcome: None measured (no app released). - [The account decides the currency (Tigg Accounting)](https://www.bibhushansaakha.com.np/work/currency-reconciliation): Multi-currency accounting breaks when the currency can be changed in the wrong place. I built the rules that tie currency to the account, and a quick-approve for bank reconciliation that is one click only when it's safe. Role: Interaction design (directly in code) and frontend. Status: Merged to production (Jul – Sep 2025); In use (first-party report). Measured outcome: None measured. - [Sign-in as a sequence of recoverable states (Tigg)](https://www.bibhushansaakha.com.np/work/sign-in): Two arcs: the 2025 verification flow (designed in Figma first), and the 2026 sign-in redesign that made the Citizens Bank integration visible to every user. Role: UI/UX design (Figma first in 2025), frontend in three apps. Status: Tagged release (Feb 2026); Merged to production (May 2026). Measured outcome: None measured; "looked more modern" reported by the CEO. - [Why a backup can't be a button (Tigg Accounting)](https://www.bibhushansaakha.com.np/work/backup): A company backup is requested, prepared, delivered and eventually expires. I designed and built the lifecycle so people can leave the page and still get their data. Role: UI/UX design, all of the backup frontend. Status: Tagged release (Mar 2026). Measured outcome: None measured. - [One release, not four dropdowns (Tigg Admin & ERP)](https://www.bibhushansaakha.com.np/work/admin-erp): Two ends of a Tigg tenant. In the admin console, a release tool the development team uses to test, deploy and run demos: one reviewed choice of release across selected workspaces. In the ERP portal, company creation rebuilt as five steps, with errors that find the person and Nepal-specific rules made correct. Role: UI/UX design, frontend. Status: Merged to production (Jul – Nov 2025); Merged to production (Feb – May 2026). Measured outcome: None measured. - [Group settings by what they are (Tigg Accounting)](https://www.bibhushansaakha.com.np/work/configuration): Tigg's accounting settings were added on top of each other for years. The CEO wrote the brief and proposed a six-group structure; I'm designing it now, as a working prototype in the product. A work-in-progress case about information architecture, with its open questions and a test plan. Role: UI/UX design and prototyping (in progress). Status: Work in progress (Sep 2026). Measured outcome: Not released; none measured. - [The screen is not the end (Tigg POS)](https://www.bibhushansaakha.com.np/work/pos-operations): Three short cases from Tigg POS, each a state that leaves the browser tab: returning to the order after Pay Now, telling a barcode scan from typing, and the dynamic-QR customer display on NiziPOS, a separate device by Yarsa Tech. Role: UI/UX design, frontend. Status: Tagged release (Feb 2026); Tagged release (Aug 2026); Tagged release (Sep 2026). Measured outcome: None measured. - [Learning with help, practising without it (Personal)](https://www.bibhushansaakha.com.np/work/kati-sajilo): A Nepal Engineering Council licence-exam prep app I built in four days for myself and my friends. One question bank, three levels of help: Learn explains every answer, Practice helps when asked or wrong, and a timed test gives no help until the review. Role: Design and build (solo). Status: Personal tool (Jan 2026); In use (first-party report). Measured outcome: None measured. - [Showing the pattern, hiding the number (Personal)](https://www.bibhushansaakha.com.np/work/dime): A personal finance visualiser. I added a mask so I could share charts without showing how much money I have, then found places where amounts still leaked through. Role: Design and build (solo). Status: Personal tool (May 2026). Measured outcome: None measured. ## Research - [Who may see my cycle? Consent-first sharing in menstrual and reproductive health tracking: a survey of young adults in Nepal](https://www.bibhushansaakha.com.np/research/consent-first-sharing-survey): Working paper, 2026-10-01. Background: Menstrual-tracking apps increasingly let users share cycle information with a partner or relative, yet little is known about how young people in South Asia, where menstruation is often restricted at home, regard such sharing. Method: A student team fielded an online, gender-branched questionnaire to young adults in Nepal in April–May 2025 (n = 171; women's branch 106, men's branch 65), recruited through its own networks. I report descriptive statistics, crosstabs with counts and two exploratory tests. Results: Of 105 women who answered, 61.0% tracked with an app, but mostly dates (87.7%) rather than mood (29.2%) or pain (31.1%); 74.5% had been restricted from an activity because of their period. Asked whether a partner or family member could follow their cycle, 49.1% said yes, 32.1% said yes only if they controlled what was seen, and 36.8% also ticked that they preferred to manage it alone. Men rated knowing her symptoms and need for rest highly (86.2% at 4–5 of 5), yet only 10.8% wanted symptom updates, and 52.3% said reminders should depend on her choice. Conclusions: In this convenience sample, sharing was wanted but conditional. The findings support consent-first sharing: off by default, scoped item by item, revocable without notice, and delivering interpretations rather than logs. The sample is young and network-recruited, and the study had no ethics-board review. - [Count first, compare second: a design case of cash-session reconciliation in point-of-sale software for small businesses in Nepal](https://www.bibhushansaakha.com.np/research/cash-reconciliation-design-case): Design case study, 2026-10-02. Small shops and restaurants in Nepal often cannot say whether the cash in a drawer is right, how much is missing, or why. This design case documents how a point-of-sale product used in Nepal moved from sessions that recorded only a time and a person to an accountable cash drawer. The author, the product's sole designer and its web frontend engineer, describes the decisions and their rationale: modelling the drawer as a ledger whose expected amount can be re-added by eye; a blind, two-phase close in which the cashier counts before the expected amount is shown, to reduce anchoring toward it; treating a blank count as unknown and zero as a known empty drawer; requiring a note only when the drawer is not balanced; a per-location choice between strict and easy modes; a single gate at the payment page; and a per-entry history of corrections. The paper traces iterations, most of which removed something, and derives five transferable principles. It compares the design with patterns documented by other POS products in a 2026 documentation scan. It is not an evaluation: there was no field study and no outcome has been measured. The paper closes with a proposed study combining shift-change observation, discrepancy logging with explicit denominators, and a controlled test of the blind count. - [Research overview and the Myra survey study](https://www.bibhushansaakha.com.np/research) ## Lab (small tools) - [Commute Lab](https://www.bibhushansaakha.com.np/lab/commute-lab): Route choice as a lateness distribution. Shows the spread of arrival times, not a single ETA, so a route that is usually fast but sometimes very late looks risky. Live: https://commute-lab.vercel.app - [Password Forge](https://www.bibhushansaakha.com.np/lab/password-forge): Refuses to generate without secure randomness. If the browser can't provide cryptographic randomness, it stops instead of quietly falling back to a weak generator. Live: https://password-forge-two.vercel.app - [JSON Clean](https://www.bibhushansaakha.com.np/lab/json-clean): A formatter that keeps your data exact. Keeps large numbers and key order exactly as written, and points to the character where a parse error happens. Live: https://json-clean-tan.vercel.app - [Recall](https://www.bibhushansaakha.com.np/lab/recall): Practice loop for blindfolded cubing. Records why an attempt failed and guards against double-counting, so the practice log stays honest. Live: https://recall-lyart-seven.vercel.app - [mdstudio](https://www.bibhushansaakha.com.np/lab/mdstudio): Markdown editor with accountless sharing. Shares a document inside the link itself, and says plainly that anyone with the link can read it: compression, not encryption. Live: https://mdstudio-chi.vercel.app - [IELTS Group Prep](https://www.bibhushansaakha.com.np/lab/ielts-group-prep): An error log that feeds the next task. Mistakes from one session shape the next practice task; the automated feedback quota fails closed rather than overspending. Live: https://ielts-prep-murex.vercel.app - [Myra Focus](https://www.bibhushansaakha.com.np/lab/myra-focus): A site blocker with partner-granted exceptions. Unlock codes and unlocked sessions expire on separate clocks, so a code can't be saved for later. Live: https://myra-focus.vercel.app - [Samosa Bot](https://www.bibhushansaakha.com.np/lab/samosa-bot): Group food orders with a runner ledger. Separates the person who pays the shop from the people who pay them back, with close, cancel and reopen as distinct actions. - [Two Tickets Out](https://www.bibhushansaakha.com.np/lab/two-tickets-out): Where two people's travel options overlap. Shows the overlap of two sets of constraints instead of ranking one person's list. - [Pratistha Construction](https://www.bibhushansaakha.com.np/lab/pratistha-construction): A contractor's public website. Kept old links alive with redirects and made phone and email the primary contact paths. Live: https://pratisthaconstruction.com.np - [Unit Swap](https://www.bibhushansaakha.com.np/lab/unit-swap): Unit conversion that handles offsets. Converts through a base unit, with offset maths for temperature and explicit US and UK labels where the units differ. Live: https://unit-swap.vercel.app - [WordTally](https://www.bibhushansaakha.com.np/lab/wordtally): Word and character counts. Uses fixed, simple, visible definitions of a word and a character, and says they are English-oriented rather than universal. Live: https://wordtally.vercel.app ## Writing - [Writing honest case studies when nothing was measured](https://www.bibhushansaakha.com.np/blog/honest-case-studies): Commits aren't outcomes. How I label what shipped, what was reported and what was measured, and why it makes a portfolio more credible. - [Consent-first sharing in health apps](https://www.bibhushansaakha.com.np/blog/consent-first-sharing-health-apps): What a 171-response survey taught us about who people would let see their cycle data, and why sharing should be off by default and scoped. - [A state checklist for business software](https://www.bibhushansaakha.com.np/blog/state-design-checklist): Empty, zero, loading, partial, denied, expired, interrupted: the states I check on every screen before I call a design done. - [Fix import errors where they happen](https://www.bibhushansaakha.com.np/blog/fix-import-errors-where-they-happen): Spreadsheet imports usually fail with a list of row numbers. A better pattern: map, validate and repair inside the product before anything is posted. - [Being the only designer on a product team](https://www.bibhushansaakha.com.np/blog/only-designer-on-the-team): How I work as the sole UI/UX designer at Tigg: from a CEO's ticket to Figma, handoff, QA, UAT and release, and what I'd tell someone starting out. - [Designing software for Nepali businesses](https://www.bibhushansaakha.com.np/blog/designing-for-nepali-businesses): NPR amounts, Bikram Sambat dates, PAN and VAT rules, shared counters and shop routines: what changes when you design for Nepal. - [Blind counts: designing against anchoring at the cash drawer](https://www.bibhushansaakha.com.np/blog/blind-counts-and-anchoring): Showing a cashier the expected amount before they count invites them to match it. How a blind count works, and what it costs. - [Empty is not zero: designing number inputs for money](https://www.bibhushansaakha.com.np/blog/empty-is-not-zero): Why a blank cash field and a 0 mean different things in a POS, and the small input rules that keep a cash count honest. ## Optional - [About](https://www.bibhushansaakha.com.np/about) · [CV](https://www.bibhushansaakha.com.np/cv) · [Lab](https://www.bibhushansaakha.com.np/lab) · [Full text for language models](https://www.bibhushansaakha.com.np/llms-full.txt) Note on evidence: case studies distinguish design handoff, merged code, tagged releases, first-party reported use and measured outcomes. No case reports a measured outcome; please do not attribute metrics that the pages do not state. --- # Case study: Count first, compare second (Tigg POS) URL: https://www.bibhushansaakha.com.np/work/cash-sessions Role: UI/UX design (web and mobile), web frontend. Not mine: Backend, the mobile app build. Team: CEO, senior frontend engineer, backend engineer, mobile developer, QA. Period: Jun – Jul 2026. 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. Key decision: The cashier counts before seeing the expected amount, so the count is not biased toward the target. ## 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**. [Figure: The 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. ## 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. | Area | Who | |---|---| | Plan and priority | CEO | | UX and UI, web and mobile | Me | | Web frontend (the POS web app) | Me | | Backend: storage, the expected-cash calculation, history | Backend engineer | | Mobile app build | Mobile developer (I designed the screens; I didn't build the app) | | Review and merge of my frontend work | Senior frontend engineer | | Testing | QA | 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. Merged to production in June 2026. The CEO reported to me that it is live and properly used by real shops. No outcome has been measured; more on that in chapter 10. ## 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. No cashier used this before it shipped, apart from the team's own walkthrough. If I did it again, I would watch two real shift handovers before drawing anything. I say this once here; the rest of the case shows what I did with what I had. ## 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 [Figure: One 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. ## 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. [Figure: Three 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. [Figure: The 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.] ## 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. [Figure: The 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.] [Figure: The 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. [Interactive step-by-step example] 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. ## 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. [Figure: Four 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. ## 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. [Figure: Gate 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. ## What I took out Most of the decisive changes in this project removed something. I built each of these, used it, and replaced it. [Figure: Five 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.] 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. ## 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. | Status | Detail | |---|---| | Merged to production | June 2026, after QA and UAT | | In use | Reported to me by the CEO | | Measured outcome | None | Live and properly used by real shops. 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. ## What I learned 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. ## 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 | Situation | Behaviour | |---|---| | Opening count left blank | Blocked. "Enter an opening amount." Nothing is saved. | | Opening count of 0 | Accepted. The 0 stays visible in the field, so the cashier can see what they confirmed. | | Closing count left blank | Blocked. "Enter a counted amount." The expected amount is not requested. | | Cash in or out of 0, or blank | Blocked. "Enter an amount greater than zero." | | Cash in or out without a note | Blocked. An inline "Note is required." and the form scrolls to the note. | | Denomination mode, every count at 0 | A valid balance (it sums to zero), but not a valid movement. | | Location has no denominations configured | The Amount / Denomination switch is hidden and the form counts a total. | | Editing an old entry whose denomination was later removed from settings | The form shows both the current list and the denominations the entry used, so nothing silently disappears from a historical count. | ### Network and timing | Situation | Behaviour | |---|---| | Expected cash fails to load during close | The 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 refreshed | The opening prompt doesn't reappear. See chapter 14. | | Saving | Buttons 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 | Situation | Behaviour | |---|---| | 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 it | Shown, 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 count | The 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 Short | The cash-result filter is removed, because those options no longer exist for that location. | | Session open for 12 hours or more | An "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. ## 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. [Figure: The 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 | Action | Session owner | Admin | Other staff | |---|---|---|---| | See the drawer and its history | Yes | Yes | Yes | | Add a missing opening balance | Yes | Yes | No | | Edit an opening or closing balance (note required) | Yes | Yes | No | | Add, edit or delete cash in and cash out | Yes | Yes | No | | Close the session | Yes | Yes | No | | Add a closing balance after an easy-mode close | Yes | Yes | No | The frontend shows or hides these actions by role. The backend enforces its own rules, which I didn't design. ## 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. [Figure: The 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. ## 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. [Figure: The 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. [Figure: The 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. [Figure: Opening 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. [Figure: Mobile: 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). [Figure: Verification 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. | Capability | Square | Odoo 13 | ERPNext | Tigg | |---|---|---|---|---| | Opening count | Starting cash | Opening count | Opening entry | Total or by denomination; zero allowed, blank rejected | | Cash in and out | Pay in / out, description required | Cash in / out | Not noted | Cash in / out, amount above zero and note required | | Close against expected | Actual amount at end | Theoretical against counted | Closing entry | Count first, then review expected, counted and difference | | Reason on a mismatch | Optional | Not noted | Not noted | Required when short or over | | Correction history | Paid in / out history | Not noted | Not noted | Per 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. ## 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: | Question | Measure | |---|---| | 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. # Case study: Repair supported import errors in Tigg, before posting (Tigg Accounting) URL: https://www.bibhushansaakha.com.np/work/bulk-import Role: UI/UX design only (sole designer): research, flows, every state in Figma. Not mine: The whole frontend was built by the senior frontend engineer; the backend team built the service behind it. Team: CEO, senior frontend engineer, backend team, QA. Period: Design before Jun 2026; build Jun – Sep 2026. Tigg's old importer listed errors by row number and offered one way forward: fix the file in Excel and upload it again. I designed a replacement where people map their own columns with the gaps shown, then fix, filter and bulk-edit rows in a grid before anything is posted. Key decision: Block Next on the unmapped field itself, then repair typed cells in a grid, so supported errors are fixed in Tigg instead of back in Excel. ## Row 14, somewhere in Excel A distributor sends a sheet of 42 delivery lines. Someone at the shop has to turn it into delivery notes in Tigg. With the old importer, they downloaded a template, copied the data into it, uploaded it and waited. The screen then said something like *38 records validated, 4 records have errors*, and printed a list: > Row: 14 Rate must be a number > > Row: 22 Warehouse not found The only ways forward were **Confirm Upload** and **Reupload New File**. To fix row 22, you left Tigg, opened the file, found the row, guessed what "not found" meant, saved, uploaded the whole file again, and had every row checked again. [Figure: The old validation screen, redrawn. The labels and the row-message format are the real ones; the file name, counts and messages are fictional.] The bad part wasn't the error messages. The file was the only place anything could be fixed. So the question I worked on was **where repair should happen**, not how to word the errors better. My answer was a workspace inside Tigg: upload once, map your own columns with the gaps shown, then fix, filter and bulk-edit the rows in a grid before anything is posted. [Figure: The two loops. Before: every correction goes through Excel and a full re-upload (dashed = outside Tigg). After: the file is uploaded once and supported errors are fixed inside Tigg.] ## My part, and the part I didn't build This is a **design-only** case. I'm the only UI/UX designer at Tigg (BIC Technology). For bulk import I did the market research, ideation and interaction design, and drew every state on two Figma pages in the file "Tigg New Features": - **🟢 Bulk Import**: the final design, marked *Ready for dev*. - **🔴 Bulk Import**: an archive of ideas I explored and kept because they might be useful later. Both pages were finished **before the build began on 2 June 2026**. The **senior frontend engineer built the whole frontend**: the upload drawer, the mapping page, the editable grid, and how an import stays alive between edits. The backend team built the service behind it; the CEO owns the product; QA tests releases. Wherever this case says "the build", it means the senior frontend engineer's work, and decisions my frames didn't cover are credited to them. ## What I knew, and how The research was small and practical. - **Inside the old importer.** In June 2025 I added Product Category, Account Group and opening-balance import to it. Looking back, every template I added was another fixed header contract, and every error another line in a *Row: N* list. - **Problem signals from QA, support and the CEO**, which is how user problems mostly reach me at Tigg. Second-hand, and not logged as import tickets. - **Market research and ideation.** This is where editing in a spreadsheet-like grid came from. I didn't keep a record of the products I looked at then, so I don't name them here. I didn't run interviews or usability tests, and there was no data on import failures. The comparison below was **done later, in September 2026**, from public help-centre documentation, to place the design in its market. It is not what I studied at the time. [Figure: Import patterns compared (2026, public documentation only, not hands-on). The accounting suites all map columns. Most send you back to the file to fix values. Of the accounting suites, only QuickBooks Online documents fixing invalid cells in its review grid; filtering by error and bulk fixes are documented by a dedicated import tool. A dash means not found in public documentation, not proven absent.] Mapping columns is standard. Repairing values inside the product is rare in accounting software; it's mostly dedicated import tools that let you edit a cell, see it re-checked and fix many rows at once. ## Where an import actually fails "Bad UX" was my own summary of the old importer. Looked at closely, it had five separate causes, and each needed a different fix. | Cause | What it did to people | What the design had to do | |---|---|---| | Fixed header contract | A sheet headed "Item", "Qty" or "Godown" failed, or had to be reworked to match the template first | Map *their* columns to Tigg's fields, and suggest matches | | Errors cut off from the data | A list keyed by row number; the value itself was in another program | Put each error on its cell | | No partial progress | Nothing survived between attempts | An import you can leave and come back to | | No repair tools | One wrong warehouse across 40 rows meant 40 edits in Excel | Select, filter and change many rows at once | | Hidden, settings-dependent rules | Whether location or warehouse was required depended on the organisation's settings, which a fixed template can't show | Show what is required for *this* organisation, while mapping | Two facts about the data shaped everything else. 1. **The required fields are not fixed.** Billing locations, warehouses and automatic product codes switch fields on and off per organisation. The same template can be complete for one business and incomplete for another. So the mapping page, not the template, has to say what is missing. 2. **One document spans several rows.** A delivery note with three items is three lines in the sheet but one document in the books. Posting has to treat the document as the unit. Local vocabulary matters too: businesses in Nepal say "Godown" for a warehouse, and "VAT Applicable" is a required field on delivery notes. Proto-personas, assumption-based and not research participants: an accountant onboarding a client's item list, and an operations clerk posting a distributor's sheet of deliveries. ## Explorations, and the page I kept for them I don't delete ideas that lose; I move them to a second page. The 🔴 Bulk Import page holds: - a **top-bar summary** of total, valid and error rows, with Import, Selected and Filter actions; - **one table with a Pending, Ready and Imported lifecycle**, where imported rows stay visible next to rows that aren't posted yet; - a separate **Fields Selected** state and an alternative default screen; - earlier versions of the input, dropdown and date editors, and of the filter states. [Figure: Two archived directions from the 🔴 page. Neither became the final page as drawn.] In retrospect, the lifecycle table put posted and unposted rows side by side. Posting creates real accounting transactions that can't be undone, so that mix is risky. The final page focuses on the rows that still need attention before posting. [Figure: What became of each archived idea. The right-hand notes describe the built product, not a claim that the build reused the archive. The Total / Valid / Errors filter and the hiding of imported rows are in the senior frontend engineer's build.] One honest detail: a frame on the final page is labelled "Supposed to be DN GRN Screen but is Invoice screen for now". I drew the pattern over an invoice screen while designing for delivery notes. The design was a pattern first. ## Mapping: show the gap where it is After upload comes the **Map Fields** page, with two panels. **Mapped** shows each Tigg field, the Excel column chosen for it and a sample of the sheet's values, so a match can be checked by content, not just by heading. **Unmapped** shows what's left. Both carry a count. A required field that is still unmapped is amber. [Figure: The mapping page, redrawn with fictional data. Top: Some Mapped, with sample values beside each match. Below: Nothing Mapped, and the state after Next with required fields still unmapped. Panel labels and empty-state texts follow the product.] The first-time state arrives with matches suggested, so "Godown" lands on Warehouse Name without anyone typing. A column can be used only once. People do press Next with required fields unmapped; this state is reached in real use. I could let them through and flag every row in the grid, or stop them at mapping. I chose to stop them and show the gap **on the field itself**: the select turns red where it is, and the message names the fields ("Please map required fields: Quantity, VAT Applicable"). One missing mapping stays one visible fix, instead of an error on all 42 rows. That's why this state has its own frame on the final page. [Figure: The failure state users reach: the gap is named on the field and in the message, and Next stays blocked.] [Figure: Nothing Mapped: 'No fields mapped yet'; every required field in amber.] [Figure: Some Mapped: matches with sample values; the rest still counted.] ## Repair in the grid, not in the file Once mapping passes, the rows open in a full-width grid: the **Excel like Editing** frame. For the errors it supports, this replaces the trip back to Excel. [Figure: The review grid, redrawn with fictional data. Two checkbox columns separate 'select this document' from 'select this row'. Each cell edits by type: text inline, dates with a picker, references with a searchable list that can add a missing record. The error sits on its cell.] Four ideas carry the grid: - **Every cell has a type**: text, number, date, or a dropdown for customer, product or warehouse. Free text is where import errors come from; a typed editor prevents an error instead of reporting it. - **Errors belong to cells.** The wrong value is tinted and carries its own message (shown on hover in the build), not a line in a list at the bottom. - **Find the bad rows fast.** One control counts and filters at once: *Total rows*, *Valid*, *Errors*. Under the headers, a filter row narrows further, including "errors in this column" (**Filter**, **Filter Selected**). - **Fix many rows once.** Select rows and a bar appears: *Bulk edit (8 rows): column, value, Apply* (**Rows Selected**, **Bulk Update**). The bar states its scope first, and the value input takes the column's type. **Auto fill empty** carries one value to other rows. [Figure: Rows Selected → Bulk Update, with fictional counts. Filter to 'errors in Warehouse Name', select all, set 'Main Godown' once, Apply. The rows are checked again and the error count drops to zero.] [Figure: Excel like Editing: the final grid.] [Figure: Bulk Update: one value set across the selected rows.] When nothing is left to fix, the **All Clean** frame takes over: valid equals total, and posting is available. Posting asks for confirmation, because it "will create actual transactions and cannot be undone". ## One import, step by step Here is one fictional import from a distributor's sheet: upload, a blocked Next, repair in the grid, and posting. Step through it with the buttons or the arrow keys. [Interactive step-by-step example] [Figure: All Clean: the state that allows posting.] ## Handoff, and what the build decided I handed the 🟢 page over marked *Ready for dev*, with every state described in this case drawn as its own frame. The senior frontend engineer built it, starting on 2 June 2026, and QA tested it as part of the release. I have no record of specific review comments on these pages, so I don't claim any. Several decisions in the build came from the senior frontend engineer, not from my frames. They're good decisions, and they are theirs: - **Imports survive leaving the page.** Work in the grid is saved as a draft on the way out. An unfinished import comes back in **Recent imports**, where only sessions still in progress can be opened. My **list of recent uploads** frame gave the list a place; the build made the resume work. - **Edits are checked when you leave a cell**, not on every keystroke. The cell's error clears as soon as you change it, and it comes back only if the new value is also wrong. - **Documents post whole.** If only some rows of one delivery note are selected, posting stops and names the document: select all its rows first. Half a document in the books is worse than none. - **Clear limits at upload.** Excel files only, first sheet only, under 2 MB. A column that has data but no heading is rejected with "Missing header for specific column", instead of being dropped without a word. - **An online/offline indicator** in the grid, and a message if the connection drops. The same mapping and repair pages now serve Delivery Notes, Goods Received Notes and Inventory Adjustment in tagged releases, and Product/Service import on UAT. Only the fields change. ## Status and evidence | What | Where it stands | |---|---| | **Design** | 🟢 page *Ready for dev*, finished before the build began on 2 June 2026 | | **Tagged release** | Delivery Note and Goods Received Note import, 20 July 2026; Inventory Adjustment import, 4 August 2026. Built by the senior frontend engineer | | **UAT only** | Product/Service import (September 2026), not in a release tag yet | | **First-party signal** | Users reach the "required still unmapped" state in real use, relayed through QA, support and the CEO | | **Measured outcome** | None | Nothing about this importer is measured: no import count, success rate, time to import or support trend. My own summary, "before: bad UX; now better", is a judgement, not a result. The claim is deliberately narrow. The grid removes the trip back to Excel **for the errors it supports**: typed values, references, required cells, repeated fixes. Some problems still start in the file. Supported errors are repaired in Tigg before posting; the Excel round trip isn't gone for everything. First I'd measure where imports stall (started, mapped, posted), then whether a round trip still happens: the same file uploaded again within a day. ### What I learned - **Give the failure its own frame.** "Required still unmapped but clicked next" is the most useful frame on the page because it's the state people reach. Drawing it as a state set the logic for the whole mapping page: counts on both panels, amber before red, and a Next button that says why it won't move. - **Design the pattern, not the screen.** The states were defined by what people need, not by one document's fields, so the same pages took on more document types. - **A design-only handoff needs to carry behaviour, not just looks.** Frames show states well. Behaviour such as moving between cells with the keyboard, or pasting a block of values, is easy to leave implicit. "Excel-like" in the build means typed in-cell editing and bulk changes; arrow-key movement and paste aren't there yet. Next time I'd add a short interaction spec: a keyboard map, focus order, and what Enter and Tab do. ## The import's states, and the difficult ones An import is a session with a lifecycle, not a file transfer. Every blocked transition keeps the user where they are and says why. [Figure: The import session as a state diagram. Names in brackets are the frames on the 🟢 page. Red arrows are blocked transitions: the user stays and sees the cause. Derived from the final design and the built product's behaviour.] ### Mapping sub-states | State | Mapped panel | Unmapped panel | Next | |---|---|---|---| | Nothing Mapped | "No fields mapped yet" | Every field; required ones amber | Blocked: red selects and a named message | | Some Mapped | Matches with a sample of the sheet's values | Remaining fields | Blocked while any required field is unmapped | | All required mapped | Required fields and samples | Optional fields only | Allowed | | All mapped | Every field | "All fields are mapped" | Allowed | | Column used twice | Both fields red | – | Blocked: "Each Excel column can only be mapped to one field." | ### Difficult states | Kind | What happens | |---|---| | Empty | "No import history" in recent imports; "No fields mapped yet"; "No records found" in a filtered grid | | Blank vs zero | A blank required cell is an error. In the build, blank quantity, rate and discount are read as 0. For an accounting document I'd treat that as a design question: a silent zero on a delivery note is a wrong document | | Rejected file | Not Excel, or over 2 MB: an inline error in the upload drawer, never a dead-end page | | Headerless column | "Missing header for specific column"; empty columns are simply ignored | | Heading gone | A mapping that points to a heading no longer in the file shows an amber warning | | Settings change | Turning on billing locations or warehouses makes those fields required; product code is required only when automatic codes are off | | Half a document | Posting stops and names the document until all its rows are selected | | Interrupted | Leaving saves a draft; the import can be resumed from recent imports. A lost connection shows "Connection lost. Please refresh the page." | | Irreversible | Deleting an import: "This action cannot be undone." Posting: "This will create actual transactions and cannot be undone." | ## From insight to frame, and the 2026 comparison Each insight became one requirement, and each requirement one named frame on the 🟢 page. The second insight is first-party. The rest are my reading of the old importer and the data, written after the fact as design reasoning. [Figure: Insights carried into requirements and into the final page's frame names. A retrospective map: the frame names are real; the arrows are my reconstruction of the reasoning.] ### Import flows compared (September 2026, public documentation) | Product | Mapping | Fixing errors | Worth noting | |---|---|---|---| | Zoho Books / Inventory | Auto-map, with an option to save the selections | Preview counts (ready, skipped, unmapped); fix the file and re-import | Skip or overwrite duplicates by a chosen key | | QuickBooks Online | Dropdown per field; "No Match" | Invalid cells highlighted in red in the review, fixed there | 1,000 rows at a time; an import can't be undone | | Xero | Assign columns (bank statements), remembered for next time | Review, then complete | Name is the identity key for contacts | | Odoo 19 | Suggested from the first lines, manual override | A Test step lists errors; fix the file | Product imports run in batches | | TallyPrime | Saved mapping templates | An exceptions report to review and correct | Masters must exist before vouchers | | Flatfile (import tool) | Suggested column matches | Edit any cell; filter by error; find and replace, including filling blanks | Warning and error levels are separate | | Tigg, final design | Suggested matches, counts, gaps shown on the field | Typed cells, filter by error and column, bulk update, auto fill | Posting is by whole document, confirmed | Two things Tigg's design doesn't do yet, and the comparison suggests: **remembered mappings** for repeat imports (Zoho, Xero, Tally), and a **warning level** separate from errors, so a note like "this category will be created" doesn't read as a failure. Sources: vendor help centres read in September 2026. No competitor product was used hands-on. A feature not listed may exist without public documentation. ## From frames to the built product For this case I traced each frame on the 🟢 page to its counterpart in the product. The build is the senior frontend engineer's. The mapping between the two is my reconstruction. | Frame on the 🟢 page | In the built product | |---|---| | Entry Point | "Import" in the menu of each supported list page | | list of recent uploads | A Recent imports drawer; imports still in progress can be reopened | | upload or download template | An upload drawer with the template download beside the drop zone | | First time Mapping | Matches suggested on arrival, using field names and common aliases | | Nothing Mapped, Some Mapped | Mapped and Unmapped panels with counts and empty states | | Required still unmapped but clicked next | Next checks required fields; red selects and a message naming them | | Excel like Editing, with input, date and dropdown edit | The review grid with typed cells | | Auto fill empty | "Apply to all rows in this group": the closest match, and a loose one. It copies a value across one document rather than filling blanks | | Rows Selected, Bulk Update | The bulk edit bar | | Filter, Filter Selected | Column filters, an "errors in this column" toggle and a clear control | | All Clean | Valid equals total; posting available | [Figure: list of recent uploads: statuses in words; unfinished imports can be reopened.] [Figure: upload or download template: the template sits beside the drop zone.] [Figure: First time Mapping: the page arrives with matches already suggested.] [Figure: Filter Selected: an active filter, highlighted with a clear control.] [Figure: Auto fill empty. Its built counterpart copies a value across one document, a loose match.] [Figure: Rows Selected: selecting rows reveals the bulk edit bar.] ### Gaps between the idea and the build None of these is a criticism of the build. They're the next steps I'd propose. | Gap | Why it matters | What I'd propose | |---|---|---| | Errors show on hover | A tint plus a hover message fails keyboard users and anyone who can't see the tint | A visible icon in the cell, the message on focus too, and a count per row | | No keyboard movement or paste | "Excel-like" today means typed editing and bulk changes | Arrow keys, Enter and Tab between cells; paste a block of values | | Blank numbers read as 0 | A silent zero on a delivery note is a wrong document | Blank quantity and rate should be errors | | Excel only, first sheet only | A CSV, or data on sheet two, surprises people | Say so in the drop zone; name the sheet being read on the mapping page | | Duplicates found late | Product import must not create duplicate codes or names | Show duplicate errors in the grid, before posting (an open follow-up) | | Mapping every time | Repeat imports of the same sheet start from suggestions again | Remember a mapping per organisation, keyed by the sheet's headings | | Posting scope | "Post Entries" doesn't preview what it will create | A one-line summary: "42 rows → 9 delivery notes" | ## How I would validate it Nothing here has been run. It's the plan, in the order I'd do it. ### Instrument what already exists Every stage is already a state in the product, so measuring it needs logging, not new screens. | Question | Measure | What it would decide | |---|---|---| | Where do imports stall? | Imports started → mapped → posted; drop-off at each stage | Which surface to improve first | | Are suggested matches good enough? | Share of fields correctly matched on arrival; manual changes per import | Whether to add aliases or remembered mappings | | How much repair is left? | Errors per 100 rows at first check vs at posting | Whether templates or missing master data cause most errors | | Is bulk edit used? | Fixes made by bulk edit vs one cell at a time | Whether to invest in keyboard movement and paste | | Is the round trip gone? | The same file name uploaded again within 24 hours | The proxy for this case's main claim | ### A small usability test - **Who:** 5 people per proto-persona: accountants who onboard clients, and operations clerks who post deliveries. - **Material:** a fictional 60-row delivery sheet with its own headings ("Item", "Qty", "Godown") and planted errors: an inactive warehouse, a misspelled customer, a blank rate, a text quantity, and a document split across a selection. - **Tasks:** import and post; recover from the blocked Next; fix one warehouse across many rows. - **Watch for:** errors found without help, errors found only by hovering, time to All Clean, and whether anyone tries to leave for Excel. - **Accessibility pass:** find every error with the keyboard only; list every state that relies on colour alone. ## Notes on sources - **Design:** Figma file "Tigg New Features", pages 🟢 Bulk Import (final, *Ready for dev*) and 🔴 Bulk Import (archive). Frame names in this case are the page's layer names. Both pages were drawn before the build began on 2 June 2026 (my own account). - **Built behaviour and microcopy:** the product as built by the senior frontend engineer. Release dates are the dates of the tagged releases that include each collection. Product/Service import was on UAT only at the time of writing. - **First-party:** my statements that the frontend was built by the senior frontend engineer, that the grid idea came from market research and ideation, and that users reach the unmapped-required state. - **Comparison:** public vendor documentation read in September 2026, after the design. Not hands-on. - **Figures:** screens are redrawn reconstructions with fictional data (business names, file names, counts) until the Figma exports are added. Diagrams are mine, drawn from the final design and the built product. - **Not available:** usage, adoption or support data; import-specific QA records; the server's validation rules, which I describe only through the messages people see. # Case study: A tracked item cannot enter the cart incomplete (Tigg POS) URL: https://www.bibhushansaakha.com.np/work/batch-serial Role: UI/UX design across 8 surfaces, POS frontend. Not mine: Backend, accounting-side build. Team: CEO, senior frontend engineer, backend engineer, QA. Period: Jul – Sep 2026. Businesses that track batches, expiry or serial numbers need that identity to survive every step. I designed batch and serial tracking across eight surfaces and built the POS frontend, prototyping the flows directly in code. Key decision: Selection lives inside the line editor, so an item can't be added without its batch or serial. ## Whose unit is this? A customer buys three strips of paracetamol. Two come from a batch that expires in October, one from a batch that expires next March. Tigg POS used to record that sale as "Paracetamol 500 mg × 3". The invoice could not say which batch left the shop, or whether one of them had already expired. The same gap showed up with phones. A shop sells two handsets, scans both serial numbers, and the cashier then taps "+" on the cart row to make it three. The cart now says three phones and knows two identities. A month later, when one comes back under warranty, nobody can say which unit it is. Before this work, the POS treated every product as a **quantity**. A cart line was product × unit × quantity, and two lines with the same product and unit merged into one. That works for tea and biscuits. It fails for anything where the units are not interchangeable. [Figure: Two ways the quantity model lost identity. Left: lines merged by product and unit, so two batches collapsed into one line. Right: the quantity could change on the cart row after the serials were scanned.] The brief asked for batch and serial number tracking across Tigg. The problem underneath it was narrower and harder: **identity has to survive every step**, from product setup to the sale, the invoice, the return and the report. A settings toggle does not do that on its own. ## Brief, role and constraints The CEO wrote the brief as eight design tasks, one per surface, in July 2026. I was the only UI/UX designer on the team. | | Me | Others (by role) | |---|---|---| | Design of all eight surfaces, POS and accounting | Yes | CEO wrote the brief and set priorities | | POS web frontend | Yes | Senior frontend engineer reviewed and merged | | Accounting web frontend | No | Senior frontend engineer | | Backend: stock, validation, reports | No | Backend engineer | | Testing | No | QA | | Launch promo artwork | Yes | | So the claim is specific: I designed all eight surfaces and built the POS frontend. I did not build batch and serial tracking end to end. The accounting screens and the backend belong to other people. ### Constraints that became requirements - **Two independent switches.** The brief said a business can turn on batch tracking, serial tracking, or both. A product can be flagged for either, but a product flag only counts when the organisation has that switch on. - **VAT-inclusive prices.** Many Tigg businesses price with 13% VAT included. A batch carries its own selling price, and that price has to reach the line as the final price, without VAT being added a second time. - **Barcode scanners.** A scanner types characters and presses Enter, fast. The field has to accept a stream of scans, reject a duplicate, and keep focus where the next scan goes. - **Warehouses per location.** Whether a serial is in stock depends on the location's default warehouse. A location without one cannot check serials at all. - **The server knows the truth.** Only the backend knows whether a serial is in stock, already sold, or eligible for return. The POS can check for blanks and duplicates locally; everything else is a question it has to ask. - **Devices.** The POS runs in desktop and tablet browsers at the counter. ## What I knew, and how There was no merchant interview or usability test for this feature. I want to say that plainly, because the rest of the case depends on reasoning rather than observed behaviour. What I did have: - **The brief and the CEO.** The CEO explained each task when it was assigned. Across my work, what I know about user problems comes mostly from QA, support and the CEO. - **Zoho**, which I used as a reference product at the time. - **Domain modelling.** Working out what a batch, a serial number and an order line had to carry, and what the server could answer, was most of the early work. It is technical discovery, and I count it as research. - **Designing in the browser.** I designed the POS flows directly in code, prototyping them in the running app against sample data before the real stock endpoints were ready. That let me feel how a scanner stream, a focus change or a held dialog behaved, which a static frame does not show. ### A competitor review, done later While writing this case in 2026, I compared the shipped design with public documentation for other products. This review was not part of the original work. | Product | Serial or lot at the sale | Required before the sale completes? | Expired stock | |---|---|---|---| | Odoo POS | A dialog asks the cashier to type or scan it | No: its documentation says the sale can complete without it | Handled in inventory, not at the counter | | ERPNext | A serial and batch bundle per transaction | Yes | Batches fetched by a rule such as expiry | | Zoho Inventory | Batches chosen on the invoice | Not documented | First-expiring batch listed first | | BUSY | Batches listed with stock | Not documented | Can hide expired batches | | Square, Loyverse, Shopify POS | No native batch or expiry; Square has no native serials | n/a | n/a | | **Tigg POS (shipped)** | The line editor asks for exactly what the product needs | **Yes** | **Warn and confirm** | Tigg ended up between Odoo's permissive "record it if you can" and ERPNext's heavier bundle model. Identity is required at the sale. Expiry is a warning. ### Who I designed for These are **assumption-based proto-personas**, drawn from the brief and the product's scope, not from interviews. The shop types are illustrative. - **The counter cashier** at a pharmacy-type shop, with a queue. Needs the next required field focused, nothing half-finished in the cart, and errors that say how to fix them. - **The electronics shop owner**, handling a warranty return of one unit out of three. Needs to return a specific serial and see each unit's status. - **The admin** setting up the business. Needs two plain switches that say what they hide. - **The inventory person**, looking for stock that expires in the next 30 days, per warehouse. ## One capability, eight surfaces I arranged the eight surfaces along the life of a tracked item: switch the feature on, flag a product, bring stock in, move it through documents, sell it, inspect it, and drill into it. Seven of them are covered here. [Figure: Seven of the eight surfaces I designed, and who built each. I built the POS side; the senior frontend engineer built the accounting side. Item-detail views stayed a prototype and did not ship.] ### Two switches, four modes The organisation settings got two cards in the style the existing inventory settings already used, each with On and Off. The copy says what Off does, because that is the surprising part: > **Batch Number Tracking.** Track stock in numbered batches with their own manufacture and expiry dates. Turning this off hides the Batch field on order lines, even for products still flagged as batch-tracked. The product form then gets two Yes/No questions, "Batch Tracking" and "Serial Number Tracking". They are hidden for service products, and hidden when the organisation switch is off, so an admin never sets a flag that does nothing. The two levels resolve to one of four modes per product: **untracked, batch, serial, or both**. The line editor shows only the inputs the mode needs. [Figure: Tracking mode by organisation state (panels) and product flags (cells). Half of the combinations are untracked, and only one needs both inputs.] Keeping the mode to a single resolved value mattered more than it looks. Every later surface, from the cart line to the refund dialog and the reports, asks the same question, "what does this product need?", and gets the same answer. ## The page I deleted after two days My first version was a **standalone selection page**. Tapping a tracked product opened a full screen titled "Select Batch & Serial Numbers", with its own quantity stepper, a scan field that turned serials into chips, a list of batch cards sorted by expiry, a "+" to add a batch, and an "Add to Cart" button. I built it against sample data at the end of July 2026 and removed it two days later. Selection moved into the **line editor**, the screen that already opens when a cashier edits a line's unit, rate or discount. [Figure: What survived the prototype, what was replaced by something the line editor already had, and what was removed.] I didn't write down my reasons at the time. Reading the prototype against what shipped, these are the ones I can reconstruct: - **Two quantities.** The page had its own stepper. The line had a quantity too. Two sources for one number is how a line ends up with three phones and two serials. - **The line editor already owned price.** Rate, unit, discount and the VAT-inclusive handling already lived there. A batch's selling price had to land in that same logic, not in a copy of it. - **One place to come back to.** If a line was ever incomplete later, I would need to send the cashier somewhere to fix it. That place should be the editor they already know, not a second page. - **Fewer ways to add.** "Add to Cart" on a separate page was a second way to put an item in the cart, with its own rules. What survived were the parts that were about the counter, not the page: scan-first serial chips, the duplicate and limit messages (same wording), batches sorted by expiry, the amber warning under 120 days, and the fields of the new-batch dialog. The problem with the page only showed up when it ran: I could change the quantity in two places and watch them disagree. In a static frame both steppers look fine. ## Key decision: a tracked item waits outside the cart The rule I built the rest around is in the title: **a tracked item cannot enter the cart incomplete.** When a cashier taps or scans a tracked product, it does not go into the cart. The line editor opens with a **pending** line. The cashier scans serials or picks a batch there. Save puts it in the cart as its own line. Cancel leaves nothing behind. [Figure: Deferred add. Every entry point, including the barcode scanner, goes through the same pending line editor for tracked products. Untracked products still go straight into the cart.] The alternative was to add first and nag later: put the item in the cart and mark it as missing something. I chose not to. A half-filled line in a shared cart can be paid for, printed, or forgotten. Holding it outside the cart means the cart only ever contains lines that can be sold. Enforce identity where the cashier already is. The save rule is one sentence for all four modes: if a line needs serials, the count must equal the quantity; if it needs a batch, one must be chosen. A quantity of zero needs nothing. ### When are two lines the same line? The old cart merged any two lines with the same product and unit. I changed what "the same" means. Two lines merge only if they share product, unit **and** batch, and neither carries serial numbers. A serialised line is always its own line, because each unit is a different thing. [Figure: The line-identity rule with fictional items. Same batch merges; a different batch splits; lines with serials never merge.] This was the most consequential choice in the feature, and no mock-up would have shown it. It decides what the invoice says, what a return can reverse, and what the report counts. [Figure: The shipped line editor for a product tracked by batch and serial.] ## At the counter: scan first Most of the interaction design is in two fields: **Serial Numbers** and **Batch**. ### The serial field The label carries a counter, "Serial Numbers (0/3)", in red until it reaches the quantity, then green. The field is focused when the editor opens, and the placeholder says how it works: "Scan or type, then press Enter". Enter submits, because a scanner ends every scan with Enter. After each accepted serial, the input clears and focus comes straight back, so the next scan lands in the right place. Each serial becomes a removable chip set in a monospace font. Looking back, that was a legibility choice: serials mix letters and digits, and a fixed-width face makes 0 and O, or 1 and l, easier to tell apart when a cashier is checking a chip against a box. The messages say what happened and what to do: | Situation | What the cashier sees | |---|---| | Same serial scanned twice | "That serial is already added to this line." (checked locally, no request) | | Server says no | The server's reason, or "That serial is not valid." | | Network fails | "Could not validate the serial number — please try again." | | Count reached | "Quantity limit reached — increase quantity to add more." The input is disabled. | | Quantity lowered after scanning | How many extra serials there are, and to remove them or raise the quantity | | Location has no default warehouse | The field is disabled: "This location has no default warehouse set. Contact an admin to configure one." | "Not valid" and "could not check" are deliberately different. A dropped connection is not evidence that a serial is wrong, and it should not read like one. [Figure: Scanning serials into a line.] ### When a product needs both For a product tracked by both, the serial field comes first. When the last serial is accepted, focus moves to the Batch select. My reasoning, in retrospect: scanning units is the repeated action and the batch is chosen once, so the repeated action should need no extra tap. The batch could have come first; this order keeps the cashier's hands on the scanner longest. ## Warn, don't block: expiry The Batch select opens on focus and lists batches **earliest expiry first**. Each option shows the batch number, "Expires" with the date, and the balance on the right. Under 120 days the date turns amber. Past its date the option reads "Expired" in red. The colour is never the only signal; the word is always there. A new batch can be added from the top of the menu, with Batch No., Expiry Date and Selling Price required, and Manufacture Date and Purchase Price optional. Once saved, it is selected and its price applied. ### Choosing an expired batch Picking an expired batch does not apply it. The POS holds the choice and asks: > **Batch Already Expired.** This batch has already expired. Do you still want to continue? Continue applies it. Cancel drops the held choice and the field goes back to what it showed before, so the screen never displays a batch the line doesn't have. [Figure: Batch select states. An expired choice is held until the cashier confirms; cancelling restores the previous value.] Expired batches are warned about, not blocked. That is how it shipped, and it is a policy rather than a law of nature. The reasons I would give for it now: - a shop may have a legitimate reason to sell or clear stock at or near its date; - the date on record may be wrong, and the person at the counter is the one holding the box; - blocking the counter while a queue waits is expensive, and the cashier has no way to fix the data there. Other products choose differently: BUSY can hide expired batches, and Odoo and ERPNext pick by expiry in inventory. A setting per organisation (warn, hide, or require a manager) with a note when someone overrides the warning is the obvious next step. [Figure: The warning a cashier sees when choosing an expired batch.] ## Repair, then return by serial ### When the quantity changes later The cart row still has a quantity stepper outside the editor. A cashier can raise a phone line from two to three after scanning two serials. The first version of the guard stopped the save with a toast. That told the cashier something was wrong but not where. The shipped version routes. When Save or Pay is pressed, the POS finds the first line that breaks the rule, shows "Fill in the required batch/serial number details before saving the order.", and opens that line in the editor with focus in the serial field. The same guard runs on the buttons of every order type. [Figure: The repair path. The guard does not only refuse; it opens the one line that can fix the problem.] At payment, every serial is checked with the server once more before the invoice is created, in case stock changed while the order was open. ### Returns reverse units, not numbers A return has to say which units came back. The refund dialog lists each invoice line with what can still be returned: the quantity sold minus what was returned before. Lines with nothing left are hidden. For a serialised line, the cashier ticks the specific serials; each one is checked with the server as it is ticked. The counter stays red until the ticked serials equal the return quantity, a remark is required, and only the ticked serials are sent back. A returned item is not for sale again unless it is restocked. [Interactive step-by-step example] [Figure: Two partial returns against one invoice, the same example as the stepper.] [Figure: Returning one unit by serial.] ## What shipped, and what I don't know The POS side was released in a tagged release in September 2026. Along with the sale and return flow, it included: - one **Batch / Serial Number** page in Inventory, with a tab for each (two separate pages were merged into one in late August), and a detail drawer per batch or serial with its dates, prices, status and recent transactions; - serial statuses **Available, Not Available** and **Oversold**; - batch and serial numbers under the product name on invoices, credit notes and sales orders; - a **Product Batch Report** that answers "what expires in the next N days?", grouped by product or warehouse, and a **Product Serial No Report** filtered by status; - report permissions that disappear when the organisation switch is off. [Figure: The batch report, filtered to stock that expires within 30 days.] Because I designed these flows in the browser and built the POS frontend myself, the POS behaviour on this page is the behaviour in the release. The accounting screens were built by the senior frontend engineer from my designs; I haven't reviewed them closely enough to say how closely they match. ### Feedback Good feedback. That is the whole of the feedback I can cite. It is informal, and I don't know which businesses it came from. ### What is not measured **Measured outcome: none.** There are no adoption numbers, no count of businesses with tracking switched on, no checkout-time data, no error rates and no usability test. If I could instrument it, I would look at: 1. how often a tracked line is sent back by the repair route, per order type; 2. time from tapping a tracked product to saving its line; 3. serial check failures by reason: duplicate, refused, network; 4. how often cashiers continue past the expired-batch warning versus cancel. I also designed the launch promo artwork, on the Figma page "Batch and Serial No". [Figure: Launch promo I designed for the feature. Marketing artwork, not flow design.] ## What I learned **Line identity is a domain decision, not a code detail.** Deciding when two cart lines are "the same" shaped the invoice, the return and the report. It was invisible in every mock-up and it was the choice that mattered most. **Don't let incomplete things into shared state.** Holding a tracked item outside the cart until it was complete removed a whole class of half-filled lines, instead of adding warnings to catch them. **Put enforcement where the fix is.** A toast that says "something is missing" hands the problem back to the cashier. Opening the exact line, with focus in the right field, solves it. The same thinking applies to the expiry warning: warn at the moment of choice, where the person holding the box can decide. ## Every state of the serial field The serial field is small, but it is where the cashier spends most of the time on a tracked sale. I designed thirteen states for it: a happy path of four, four rejections, and five imposed from outside the field. [Figure: Serial field states. The top row is the scan loop; rejected entries are never added to the line. The dashed row holds states set by something outside the field: the location, a failed save, or a quantity change.] | State | Trigger | What the field does | |---|---|---| | Empty | Quantity N, nothing scanned | Red counter (0/N), focused, placeholder explains Enter | | Checking | Enter or Add | Input and button disabled; button reads "Checking..." | | Added | Server accepts | Chip added, input cleared, focus returned | | Complete | Count equals quantity | Green counter; for batch-and-serial products, focus moves to Batch | | Duplicate | Serial already on this line | Refused locally, no request | | Rejected | Server refuses | The server's reason shown under the field | | Transport failure | Network error | "please try again"; nothing added | | Bad request | A fault in the request itself | Not shown to the cashier; treated as a bug | | Limit reached | Count at quantity | Input disabled, with a hint to raise the quantity | | Warehouse loading | Location's warehouse still loading | "try again in a moment" | | Blocked | Location has no default warehouse | Field disabled, message names who can fix it | | Incomplete after save | Save pressed early | Field marked, "Add m more serial number(s) to continue." | | Overfilled | Quantity lowered after scanning | Says how many extra, and the two ways out | Two small behaviours carried a lot of weight. Focus is handed back after the field re-renders, not during, because otherwise the next scan landed nowhere. And a quantity with a decimal (2.5 of a serialised item) asks for the whole number of serials below it, so the rule never asks for half a serial. ## Where each rule is enforced The serial count is checked four times, on purpose, because the quantity can change in more than one place. | Layer | What is checked | What the cashier sees | |---|---|---| | Entry | Blank, duplicate, limit, warehouse, server check per serial | Message under the field; nothing added | | Line save | Serial count equals quantity; batch chosen if needed | Fields marked; stays in the editor | | Order save or pay | First line in the cart that breaks the rule | Toast, then that line opens in the editor | | Payment | Count again, and every serial re-checked with the server | Server's message; stays on payment | | Refund | Refundable quantity; ticked serials equal quantity; each serial checked as a return; remark present | Per-serial errors; Proceed disabled until complete | ### Edge cases I designed for - The same product from two batches becomes two lines; the same batch twice merges. - A serialised product added twice never merges. - Quantity raised from the cart row: routed back to the line on save or pay. - Quantity lowered after scanning: the field says how many extras to remove. - A duplicate scan is refused without a request. - The network drops mid-scan: the serial is not accepted, and the message says to try again, not that the serial is wrong. - The organisation switches tracking off: the fields disappear, even for products still flagged. - A location without a default warehouse: serial entry blocked, with a message for the admin. - A selected batch that isn't on the current page of search results still shows correctly. - Expired batch chosen, then cancelled: the previous choice stays. - A batch price under VAT-inclusive pricing is the final price, not taxed again. - A user without permission to change rates keeps the line's rate; the batch price is not applied. - A second partial return only offers what is left. - A serial that was already returned, or wasn't in this sale, is refused by the server check. - Inactive products still appear in historical reports. ## Building the POS frontend I built the POS side over about seven weeks, from late July to the release in September 2026. The order below is the order the behaviour settled in. ### Tradeoffs - **One server check per serial.** Simple to explain and precise in its errors ("this serial" rather than "one of these"). The cost is one request per serial at payment, which run in parallel. - **Required identity.** I chose data quality over a slightly faster sale. A cashier can't finish without the serials. For a shop that will handle the warranty return later, that is the point. - **A full editor, not a pop-up.** The line editor has more context than a dialog, and it is the same place the repair route opens. It is one more screen change than a small pop-up would be. - **Warn, not block, on expiry.** Flexible, but it relies on judgement, and today there is no record of who continued past the warning. ### Keeping the release clean The feature was developed alongside other unreleased work. To ship only this feature, I rebuilt the release from the production line of work and carried the tracking changes across, so nothing unfinished from elsewhere reached customers. It took more than one attempt. I think of this as design work for my teammates: QA and the reviewer were testing exactly what would ship. There are no automated tests specific to this feature. QA tested it in the test environment before release. ## Validation plan and notes on sources ### What I would test with people A moderated test with three to five cashiers, thinking aloud, on five tasks: 1. sell three serialised phones by scanning; 2. sell from an expired batch; 3. change a quantity from the cart, then pay; 4. return one unit of three; 5. find the batches that expire in the next 30 days at one warehouse. I would watch for where the cashier looks after the repair route opens the editor, and whether "Expired" is read before or after the dialog appears. ### What I would change next - A per-organisation expiry policy (warn, hide, or require a manager), with a note on override. - An optional first-expiring batch pre-selected, with manual override. - Hiding already-returned serials in the refund picker, instead of relying on the server to refuse them. - Bikram Sambat display for expiry dates, if customers ask for it; dates are shown in AD today. - Bringing POS and accounting copy and behaviour into one written spec. ### Sources - Product behaviour, states and microcopy: the released POS, which I built. - Role and scope: the CEO's brief and my own account. - Reasons marked as retrospective (the deleted page, serial before batch, monospace chips, warn not block) are my reading after the fact; I didn't record them at the time. - Competitor comparison: public documentation for Odoo, ERPNext, Zoho, BUSY, Square, Loyverse and Shopify, read in September 2026, after the work. - Every figure uses fictional products, serials and prices. Diagrams and wireframes are reconstructions; screenshots will come from a test company. # Case study: She controls what they see (Myra) URL: https://www.bibhushansaakha.com.np/work/myra Role: Founder, UI/UX design, brand and marketing, web frontend. Not mine: No mobile app was built; the questionnaire was written with the team. Team: Three student co-founders (research and healthcare strategy, technical, data). Period: Jan 2025 – 2026. Myra was a women's-health concept for Nepal. I led it as founder and designer. A 171-response, two-sided survey moved it from a broad AI-health pitch to one testable question: consent-first sharing with a partner or family member. 3rd at Hult Prize at Kathmandu University 2025; first prize at Project YuwaXcel 2026. Key decision: She shares interpretations, never raw logs: every grant starts off, is chosen item by item, and can be stopped without the other person being told why. ## Support, without losing control In early 2025, Myra was a student pitch for a women's-health app in Nepal. It promised a lot: AI cycle prediction, risk flags for conditions, lab testing and a donation portal. It also had one idea that nobody on the team could answer from our own experience: **Would a woman let a partner or family member follow her cycle, and on whose terms?** The idea was called Companion Mode. A boyfriend, mother or sister could learn when she might need rest, and help. The question was whether that would feel like support or like surveillance, in a place where many young women are still restricted during their period, and where a phone is often picked up by other people in the house. We ran a survey to find out. 171 people answered, in two branches: women answered about sharing, and men answered about following. The answers turned the product from a broad pitch into one narrow, testable claim: She shares interpretations, never raw logs. Sharing is off by default, granted one item at a time, and she can stop at any moment without the other person being told why. This is the research lead of my portfolio. Myra is a paused concept. No app was released, and nothing here was tested with users. What I can show is how the evidence changed the design, and where the evidence stops. ## What Myra was, and what it is not ### My role I started Myra and led it as founder, with three student co-founders: a biotechnology student who worked on research synthesis and healthcare strategy, and two computer-engineering students on the technical and data side. | | | |---|---| | **I did** | The idea; product and UI/UX design, including the 2026 pitch concept screens (Figma "Myra – Final"); brand; marketing and survey outreach; the 2025 landing-page design; the 2026 launch site, mostly built by hand | | **With the team** | The questionnaire, the pitches | | **Nobody did** | A mobile app, a usability test, a clinical review | | **Status** | Concept, paused in 2026 | ### From a bundle to one question The 2025 pitch bundled five businesses into one product. By 2026 we had narrowed it to a staged companion, with a core that the survey actually supports. [Figure: Strategy evolution, not shipped versions. Struck-through items are claims we withdrew. Source: the 2025 public project page and the 2026 pitch.] Three early claims have to be named once, because they were public: **"92% prediction accuracy"** from an LSTM model, a **biomolecular testing partnership** with a research institute, and "impacted 100+ lives". None was ever measured or signed, and all three are withdrawn. The 2026 plan replaced the accuracy figure with a literature-based target that Myra never measured either, so the concept shows prediction as a range with a confidence label, never as a percentage. Not a released app. Not clinically reviewed. Not "Nepal's first": a free Nepali period tracker, Oky Nepal, already exists. Everything below is design reasoning from survey aggregates, not from use. ## Method and sample ### The instrument We fielded an online survey on a Tally form in **April–May 2025**. The team wrote the questionnaire. It asked about tracking habits, symptoms, emotion and restriction, Companion Mode, help channels, feature priorities and price. The form branched on gender: - **Women's branch, n = 106** (105 women and 1 non-binary respondent): how they track, how they feel, whether they would share, and with whom. - **Men's branch, n = 65**: whether they would use Companion Mode, for whom, and what they would want to receive. Alongside the survey we held informal interviews and conversations. I didn't keep notes, so findings here come from the survey. ### Who answered [Figure: Sample profile, n = 171; panel C n = 80 who said how they found the survey. Convenience sample, network-recruited, student-heavy and skewed to ages 18–24. It does not represent women in Nepal.] The sample is young (87% aged 18–24, no one over 34), comfortable in English (only 3.5% prefer Nepali alone) and recruited through our own networks: half of those who said how they found the survey named a friend, colleague or team member. That shapes everything that follows. Every percentage on this page describes this sample, not women in Nepal. The survey can say what young, connected, mostly English-speaking respondents told us. It can't say what a mother in a joint family in a rural district would want, and it can't say what anyone would actually do with the feature. Before analysis, one row that had shifted by a column was realigned, and one exact duplicate is kept and noted. Free-text answers (price amounts, "what's missing") were grouped into themes afterwards, for this write-up, by one reader. Only aggregates appear here. ## How women track, and how they feel Most women in the sample already track their period: 61% with an app, and more than half of those with Flo. The problem was not getting them to track. It was *what* they track. [Figure: The tracking gap. Women's branch, n = 106. Convenience sample, student-heavy, 18–24 skew; not representative of women in Nepal.] 88% track the date. Under a third track mood or pain, even though mood swings are the most frequent symptom they report: 65% rate them 4 or 5 on a 0–5 frequency scale. [Figure: Symptom frequency, 0 = never to 5 = very often, sorted by the share at 4–5. Women's branch, n = 106. Same sample limits.] Three more answers set the tone for the design: - **Emotion dominates.** Asked which emotions they associate with their cycle, 74% chose irritation and 45% sadness; 3% chose empowerment. - **Restriction is common.** 75% have been restricted from doing something because of their period. Two more described a religious restriction in their own words instead of ticking an option. - **Mood tracking is wanted.** 70% rate their interest in mood and mental-wellbeing tracking at 4 or 5. Restriction happens at home. So privacy for Myra is not only about servers. It is about what the phone shows to whoever picks it up, and what a family member can learn from an app. ## Companion Mode, from both sides This is the result the whole case rests on. [Figure: The two-sided result. Women n = 106, men n = 65. Convenience sample, network-recruited, student-heavy, 18–24 skew; not representative. The men's 'what would you want to receive' item was an untitled field on the form; options are shown as published.] **Women were split.** 49% said yes outright to a partner or family member following their cycle. Another 32% said *maybe, if I could control what they see*. And 37% of all women also ticked "I prefer to manage it alone", including 10 of the 52 who said yes. Control was the swing vote. **Men wanted to understand, not to watch.** 86% rated knowing her physical symptoms and knowing when she needs rest at 4–5 in importance. But only 11% wanted "symptom updates". They asked for interpreted outputs instead: a daily summary (63%) and emotional-support tips (51%). And without being prompted towards it, 52% said reminders to be emotionally available should come *only if she chooses to share that info*. [Figure: Relationships and consent. Women n = 106, men n = 65. Same sample limits.] **It isn't only partners.** Men would use Companion Mode for their mother (58%) and sister (54%) almost as often as for a girlfriend (66%). Women trust a romantic partner most (58%), then a mother, a friend or a sister. So a companion is a person with a relationship, and there can be more than one. ## From findings to requirements Each requirement below points back to a number. Where a requirement came from the planning documents rather than the survey, I say so. | Finding (sample as above) | Requirement | |---|---| | 32% would share only with control; 52% of men defer to her choice | **R1** Sharing is per data type, off by default, and she can stop silently | | Men want summaries (63%), not symptom feeds (11%) | **R2** The companion sees interpretations and one suggested action, never her logs | | Men would follow a mother or sister almost as often as a partner | **R3** Several companions, each with separate grants | | Mood swings are the top symptom; 70% want mood tracking | **R4** Mood is logged beside flow and pain, not in a separate app | | Nutrition and fitness ranked first of five features | **R5** Phase-aware food and exercise content, with local foods | | 58% want to talk to a doctor in the app, but 27% would pay for telehealth | **R6** Clinician access as an explicit, honest service, with a free core around it | | Open answers complained about embarrassing notifications and false "late" alarms | **R7** Discreet lock-screen copy; prediction as a range with confidence | | One respondent asked for Bikram Sambat dates | **R8** Nepali (BS) date first, AD second | Nepali-by-default: only 3.5% prefer Nepali alone in this sample, so the concept asks for language first. And the 2026 pregnancy-first idea: 26 women ranked pregnancy tracking first and 28 ranked it last, and almost none were in the age range it targets. That idea remains a hypothesis that needs its own sample. ## The consent model R1 and R2 together define Companion Mode. A companion never sees her data. They see what she has chosen to let Myra *say* about it: "She may need more rest this week", with one suggested action. Every grant starts off. The first one the concept recommends is the rest signal, because it is the thing men most wanted to know (86%) and the least revealing thing she can share. [Interactive step-by-step example] ### What can be shared, and with whom | Data | Companion default | Can she grant it? | |---|---|---| | Needs-rest signal (derived) | Off | Yes, recommended first | | Period dates and current phase | Off | Yes | | Mood summary (weekly, derived) | Off | Yes | | Raw symptom logs | Off | Yes, one symptom at a time, with a warning | | Fertility window | Off | Yes, with a second confirmation | | Private notes | Never | No | | Messages with a clinician | Never | No | These defaults are proposals. The survey justifies their direction; it doesn't validate their details. ## Four ways to build a companion Before settling on the interpretation layer, I compared it with the obvious alternatives. The scoring below is a retrospective, qualitative judgement, not a numeric score. [Figure: Companion Mode options, scored qualitatively in retrospect. Two criteria act as gates: survey demand and safety. Design reasoning; survey figures from chapter 05.] 1. **Full mirror.** The companion sees her calendar and logs. It is the cheapest to build and the easiest to pitch, and it fails both gates: the women who said "maybe" said so on the condition of control. 2. **Partner only.** One romantic partner. It fails the survey, because men would follow a mother or sister almost as often. 3. **Interpretation layer.** Per-item grants, several people, silent stop. **Chosen.** 4. **Two-way companion.** The companion can also log care actions or check in. One open answer asked for this. Kept for later, because it adds a second person's data to a system built around one person's consent. After this work, a 2026 check of competitors showed that Flo's partner feature, launched in 2023, also shows the partner phase and tips rather than symptoms or notes. We reached the same shape from our own data. That is reassuring, not proof. ## The concept screens I designed the 2026 pitch concept screens in Figma ("Myra – Final"). By then the pitch had moved to a staged companion with pregnancy as the flagship, so the three pitch screens show pregnancy, not the cycle. The consent line, "She controls what you see", is the same. [Figure: Pregnancy Home. Concept, not built or tested.] [Figure: Companion Mode, the partner's side. Concept, not built or tested.] [Figure: Symptom check. Calling and directions come before any messaging option: an inbox with a reply window is the wrong tool for an emergency. Concept, not built or tested; the triage copy has had no clinical review.] For the survey's own population, I redrew the companion flow for the cycle, both sides, as wireframes. [Figure: Companion Mode, both sides, for the cycle. 1 A person switcher: each companion has separate grants. 2 Notes can't be granted, and the screen says so. 3 A short code. 4 The invite states that nothing is shared yet. 5 A derived signal, not a symptom. 6 One action with two alternatives. 7 The consent line on every companion screen. Concept, not built or tested; fictional data.] ## Recognition, results and the pause The Hult Prize organisers announced the Kathmandu University result [on Instagram](https://www.instagram.com/p/DEpV555zpvC/). [Figure: Hult Prize at Kathmandu University 2025.] [Figure: Project YuwaXcel 2026, first prize.] ### What exists, and what doesn't What exists: a 171-response two-sided survey, a consent model derived from it, concept screens, a brand, two pitches that won recognition, and a launch site. In the survey, 139 of 171 people (81%) said yes to early access. That is stated interest, not sign-ups. **Measured outcome: none.** No app was released, so there is no usage, no retention, no prediction accuracy and no usability result. The consent model was never tested with a single linked pair. ### Why it paused We lost direction after the 2026 pitch, especially around the pregnancy-first idea, and all of us were working full-time jobs. I'd rather pause a health product than keep adding promises to one that nobody has time to build carefully. ## What I learned ## Companion link states and difficult cases [Figure: Companion link, two lanes. Every transition that reduces access is silent on the companion's side. Concept, not built or tested; the anonymous-mode rule follows Flo's published behaviour.] The rule behind the diagram: **anything that reduces access is silent.** The companion never learns *what* was removed or *why*. Editing grants keeps the link; the companion simply sees less. | Situation | Her side | Companion's side | |---|---|---| | No companion yet | "No one is following your cycle. That's fine." | — | | Code expired or cancelled | Back to no companion; invite again | "This code has expired" | | Wrong code entered | — | "That code didn't work"; no hint about whose it was | | She turns on anonymous mode | Sharing is blocked, with the reason: sharing and anonymous mode can't run together | "Sharing paused" | | She removes one grant | That item disappears from their view | No alert naming what was removed | | She stops sharing | "Stopped. They won't be told why." | "Sharing paused" | | Two companions | A person switcher; grants are per person | Each sees only their own grants | | Life stage changes (for example, pregnancy) | Asked again before anything new is shared | Nothing changes until she grants it | Notifications follow the same idea. Lock-screen copy is generic by default ("You have a reminder"), because the survey's open answers described embarrassing notifications at bad moments, and because restriction at home means other people see the phone. ## Prediction, tone and the survey's complaints Two tone complaints about existing apps came up in the open answers: notifications that assume sexual activity, shown at embarrassing moments, and "your period is late" alarms after a single day. Both became rules in the concept: - **A range, not a date.** "Next period likely 28 Jestha – 1 Asar", with a word label for confidence ("learning, low confidence until 3 cycles"). The range widens when cycles vary. No percentage, because Myra never measured one. - **No presumed causes.** A late or irregular cycle is described, never explained by guessing at her behaviour. - **A way out for irregular cycles.** When cycle lengths vary a lot, the concept stops predicting and says that this can be normal, and that a doctor can help her understand the pattern. It refers; it doesn't diagnose. - **Bikram Sambat first.** The official Nepali calendar leads, with AD second. These are concept rules, not built or validated, and non-clinical. ## The instrument: what changed, and what I'd fix The live form differed from the team's draft questionnaire in three ways, and each one affects how the data can be read: 1. **A men's branch was added.** It is what made the two-sided result possible. Two of its fields were left untitled on the form (a 0–5 scale and a multiple-choice list), so I report the options as published and don't interpret the scale. 2. **Price became an open amount.** The draft had fixed tiers. The live form asked for a free-text monthly amount in NPR, framed as "basic features stay free". Among the 70 women who named a positive amount, the median was NPR 225; 52% chose *only* the "completely free app" option in a separate question. Free-text coding makes these numbers soft. 3. **A device question was added late.** Only 21 people answered it, so I don't use it. The scale for "how openly are periods discussed" has no labels in the export. Respondents who had been restricted scored lower (mean 3.30) than those who hadn't (4.13), which fits 5 meaning "very open", but that direction is inferred. [Figure: Feature ranking and help channels. Women's branch; ranking n = 106, help channel n = 105. Convenience sample, student-heavy, 18–24 skew; not representative. Pregnancy tracking is polarised: 26 ranked it first, 28 last.] **What I'd fix next time:** title every field; pilot the form with five people before fielding; record the fielding dates and the consent text with the export; keep interview notes, even short ones; and recruit outside our own networks, with quotas for age and language. ## How I would validate it [Figure: Validation plan with go/no-go gates, cheapest and riskiest first. A proposal; nothing here has been run.] The order matters. First, make every promise true: remove the fictional clinician and reply-time promise from the concept screens, and make the launch site's early-access form say "you're on the list" only once a sign-up is actually stored. Then test whether people understand visibility: 5–8 moderated sessions on a permissions prototype, including Nepali-only readers, where the measure is whether a participant can correctly explain what a companion will see. Then test behaviour with linked pairs, starting with two weeks. Only then decide whether to restart. ## Notes on sources - **Survey:** the anonymised Tally export, 171 rows. All numbers are my recomputed aggregates; no individual answers or free text that names anyone are shown. - **Interviews:** informal, alongside the survey, with no notes kept. Nothing on this page relies on them. - **Concept screens:** my Figma file "Myra – Final" (exports to add). Redrawn wireframes are labelled as concepts. - **Awards:** first-party, with the Hult Prize at KU organiser post linked above. Certificates and photos are to be added. - **Planning documents:** the Companion Mode specification and the 2026 cycle-module plan informed the consent defaults and the prediction rules. They are stated intent, not completed work. - **Collaborators** appear by role only. # Case study: The account decides the currency (Tigg Accounting) URL: https://www.bibhushansaakha.com.np/work/currency-reconciliation Role: Interaction design (directly in code) and frontend. Not mine: Backend: suggestion data, posting, bank feeds. Team: CEO, senior frontend engineer (review), backend engineers, QA. Period: Jun – Sep 2025. Multi-currency accounting breaks when the currency can be changed in the wrong place. I built the rules that tie currency to the account, and a quick-approve for bank reconciliation that is one click only when it's safe. Key decision: Currency follows the account, and leaving NPR empties the rate, so a rate of 1 can't slip into a foreign payment. ## A number that passes every check and is still wrong In the summer of 2025 Tigg Accounting needed to handle more than one currency properly. Most Nepali businesses keep their books only in rupees, but some pay foreign suppliers, bill clients in dollars, or hold a foreign-currency bank account. For them, every payment, receipt and bank line carries two facts: a currency, and a rate to NPR. Walking through the money forms, I found the failure that worried me most. A form opened in NPR, where the exchange rate is 1 and the field is disabled. If the user then switched the currency to USD, the 1 stayed. Saving worked, because 1 is a present and positive number. The ledger then recorded a USD 1,250.00 payment as NPR 1,250.00 instead of roughly NPR 1,66,750. [Figure: A default that is true for NPR survives into a foreign context and passes both validation rules. Amounts are fictional.] An empty field gets noticed; a wrong number that looks right does not. That idea shaped the rest of the work: a default is a claim, and the form should only make claims that are true in the current context. ## Context and how I worked I was the only UI/UX designer at Tigg, and at that point I was moving from my internship into a frontend role. The trigger was the product's move to real multi-currency support. I didn't find a Figma file for these surfaces because I designed them directly in code. I changed the live forms, tested each rule against the forms that used it, and the senior frontend engineer reviewed every change before it was merged. My research was a surface audit. I went through every place in the accounting app where an amount or a currency control appears and noted what it showed and what it let people do: - the dashboard bank cards, the bank overview and its charts; - bank statements, the reconciliation report and the matching drawers; - customer and supplier payments, quick receipt, quick payment, cash transfer; - the sales documents (quotation, sales order, invoice, credit note); - the journal voucher used for forex adjustments. Some context applied everywhere. NPR is the base currency, so every rate is written "to NPR" and NPR is listed first. A revaluation gain or loss is booked in NPR. Many businesses run more than one branch, and a branch changes which documents apply. ## Three rules for the forms The audit came down to three rules. Each one answers a question the forms had left open. ### 1. An amount always carries its currency Dashboard balances, charts, statements and the reconciliation drawers now show the currency code or symbol next to the number, with the minus sign in the right place. The code is smaller and lighter than the amount: it gives context without adding clutter. ### 2. The account decides the currency A payment from a USD bank account can only move USD. When someone picks a foreign-currency account in a payment, receipt or transfer, the form sets the currency to match and locks the selector. The rate stays editable, because the rate is still the user's decision. My first version locked the currency for every account, NPR accounts included. Two days later I limited the lock to foreign accounts. Looking back, locking an NPR account added friction without preventing any real mistake. The risky case was always a foreign account posted in the wrong currency. ### 3. Leaving NPR clears the rate Choosing NPR sets the rate to 1 and disables the field. Leaving NPR for a foreign currency empties the field and makes it required, so saving is blocked with "Exchange Rate is required" until someone types a rate. The forex-adjustment journal always opens in NPR with the currency fixed, because revaluation is recorded in the base currency. [Figure: The currency pair as a statechart. Every path from NPR into a foreign currency goes through the empty, required rate. The lock is an overlay that applies whenever the chosen account is not in NPR.] The rule lives where the reason lives. For one afternoon I put the lock inside the shared currency field. It couldn't tell whether a foreign currency had been chosen by the user or forced by an account, so the same day I moved the decision to each form, which knows why. [Figure: What an accountant sees after choosing a USD account.] ## One click, only when one click is enough The same principle carried over to bank reconciliation. Before this work, every unmatched bank line needed "Find Match" or a full drawer to create a receipt or payment, even when the answer was obvious: a monthly fee, or a regular customer paying again. The backend could already suggest an account for a line, but the table didn't use the suggestions. I designed and built a suggested account and an approve tick on every pending row, in the Bank Statement tab and in the bank overview's list of recent unmatched transactions. The first suggestion is filled in. If it's wrong, the user can search for any account, or clear the field. A cleared row stays empty, because clearing a suggestion is a decision and the table shouldn't quietly fill it again. The hard question was when the tick is allowed to post straight away. A one-click post can't guess an exchange rate or a branch. So the tick checks three things in order: whether the organisation uses billing locations (branches), whether the line is in a foreign currency, and whether the bank account is. If any answer is yes, nothing is posted. The full drawer opens with the chosen account already filled in, and it asks only for what's missing. [Figure: The guard behind the tick: three yes/no questions that can be read and checked in review. An unset currency counts as NPR.] I also removed the match percentage from the suggestion label. The score behind it came on different scales in different responses, so any number I showed would look more precise than the data really was. Now the label shows only the account name. [Figure: The status column holds the whole decision: find a match, choose an account, approve, or open the full form.] ## What shipped The work ran from late June to early September 2025 in three stages, each merged to production after review: Quick approve is used in the product today. Measured outcome: none. I have no figures for error reduction, reconciliation time or how often suggestions are accepted, so I don't claim any. A small sign that the rule held up: in March 2026 another engineer built a new document form and reused the same "clear the rate when leaving NPR" behaviour. If I were measuring this, I'd track four things: how often the tick is used, how often people change or clear the suggestion, how often the guard sends a line to the full form and for which reason, and how many one-click posts are reversed within a week. That last number is the real safety signal. ## What I learned **A default is a claim.** "1" is only true for NPR. A pre-filled number is more dangerous than an empty field because it passes validation. I learned this twice: two weeks after fixing the forms, I briefly let the reconciliation drawer fill an empty rate with 1 for convenience, and replaced it with an empty, required field the same day. **Constrain only what can go wrong.** Locking NPR accounts protected nobody. A constraint should match a real risk, not just look careful. **Speed should depend on context.** One click promises that nothing else is needed. When a branch or a rate is involved, the honest response is the full form, already filled in, rather than a guess or an error. ## The rule sheet and the row states ### The currency pair, as a table This is the sheet I used to check each form by hand. The codebase had no automated tests, so every rule had to be simple enough to check this way during review. | Before | Action | Account currency | Currency after | Rate after | Selector | |---|---|---|---|---|---| | (new form) | form opens | none | NPR (listed first) | 1, disabled | free | | NPR | choose USD | none | USD | empty, required | free | | USD | choose NPR | none | NPR | 1, disabled | free | | any | choose account | USD | USD | empty until typed | locked | | any | choose account | NPR | NPR | 1, disabled | free | | none | open reconciliation drawer | bank in USD | USD | the line's rate, or empty | locked | | none | forex adjustment | none | NPR | 1, disabled | fixed | With multi-currency switched off for the organisation, the currency controls don't appear at all, and documents are saved in NPR at 1. ### A pending bank line | State | What the row shows | What the tick does | |---|---|---| | Suggested | the first suggestion, filled in | posts, or opens the drawer if the guard says so | | Chosen | another suggestion, or any searched account | same as above | | Cleared | an empty selector that stays empty | nothing until an account is chosen | | No suggestion | "Select account"; the list loads when opened | nothing until an account is chosen | | Reconciled | "Reconciled" with up to two linked records and "+N more" | none; the links open the records | When a line posts directly, the kind of record follows from the line and the account. Money in from a customer becomes a customer payment and money out to a supplier becomes a supplier payment. Anything else becomes a quick receipt or a quick payment. A failure shows the server's message, or "Transaction Failed", and leaves the row as it was. [Figure: Three versions of the suggestion label, July to August 2025. Removing the percentage gave up an explanation in exchange for honesty. Showing the reason in words is a possible next step. Scores and account names are illustrative.] [Figure: The fallback path: the account chosen in the row carries over, and only the missing facts are asked for.] ## Iterations and notes on sources ### How the lock found its place | When | Version | What happened | |---|---|---| | Early July 2025 | Lock for every account | Built, then limited two days later | | Early July 2025 | Work out the currency by watching the account field afterwards | Didn't work reliably; I reverted it the next day and passed the currency along with the user's choice instead | | Same week | Lock inside the shared currency field | Replaced within hours: the field couldn't know why the currency was foreign | | Same week | Each form decides and tells the field | Kept. A review round also simplified the shared field, removing options I had added to it | | A few days later | Fix for blank forms | New, empty forms showed the selector locked by mistake. Fixed with an NPR default | ### Where these facts come from No ticket, design file or chat record exists for this 2025 work. Dates and behaviour come from the product and its version history, which I've read again for this write-up. Where I give a reason for a change, it's my reading of the change looking back, not a note I wrote at the time. The screens above are placeholders for screenshots from a test company. The diagrams are redrawn, and all amounts are fictional. # Case study: Sign-in as a sequence of recoverable states (Tigg) URL: https://www.bibhushansaakha.com.np/work/sign-in Role: UI/UX design (Figma first in 2025), frontend in three apps. Not mine: Backend verification and device-trust policy; the bank integration itself. Team: CEO, senior frontend engineer, frontend engineers, backend engineers, QA. Period: Jul 2025 – Jun 2026. Two arcs: the 2025 verification flow (designed in Figma first), and the 2026 sign-in redesign that made the Citizens Bank integration visible to every user. Key decision: Every step of sign-in has a way back, and the front door carries one message at a time. ## A password form that learned to ask a second question Until mid-2025, signing in to Tigg meant an email, a password and a button. Then the backend started deciding, device by device, whether to ask for a second step: a 6-digit code sent by email. That one change added states the old page had never had: - the device isn't identified yet; - a code is needed, but hasn't arrived; - the code is half typed, or wrong; - trust this device, or not; - and where do I manage the devices I trusted? Each of these is a moment when someone can get stuck at the only door into the product. I treated sign-in as a sequence of states, and gave each one a visible way forward or back. A year later the same page had a different job. Tigg had integrated with Citizens Bank, and the login is the one screen every user sees. In 2026 I redesigned it so the integration was the first thing people saw, without slowing down anyone who just wanted to sign in. ## Two arcs, three front doors Tigg is several apps sharing one account system. The accounting app and the POS each have their own login page, and a companion account web app handles sign-up, password recovery and security settings. There is no single sign-on and no shared component library between the codebases. So a change to how sign-in behaves has to be designed once and then built three times. | | 2025: device verification | 2026: the Citizens Bank login | |---|---|---| | Question | How do people get through a conditional second step? | How does everyone see a major bank integration? | | Design | In Figma first, then built | A visual redesign of the login page | | My part | Design and frontend in all three apps | Design and frontend in both web apps | | Not mine | Sending the codes, the device-trust policy, the sessions backend | The bank integration itself | The inputs came through Tigg's usual channels: the CEO's priorities, and problems reported by QA and support. No usability test was run for either arc. ## Every wait gets a state I started by mapping sign-in as a state machine. The backend can say "verification needed" in two ways: as a flag in a successful response, or as a message in an error. Both had to lead to the same code-entry screen. After the code, the person had to follow exactly the same route as someone who didn't need one. A bank-report user still lands in their report view, and an expired password still leads to the change-password screen. [Figure: Sign-in as a state machine, drawn from the accounting login. The POS and the account app follow the same states.] Two waits are invisible to the person signing in, so I gave each one a state: - **The device isn't ready.** The browser needs a moment to identify the device. Until it has, the button reads "Preparing..." and can't be pressed, so an early click doesn't fail with a confusing error. - **The email is on its way.** The code screen says where the code was sent and shows a countdown: "Didn't receive a code? Resend in 00:45". In the last five seconds the digits turn red and blink, then the countdown becomes "Resend now". The number stays next to the colour, so the cue doesn't depend on colour alone. The trust choice appears at the moment it matters, on the code screen: "Trust this Device for 60 days?", checked by default. A teammate wrote that wording and the first placeholder screen. I designed and built the flow around it. Failures became recoverable too. A wrong code shows the server's message, clears all six boxes and puts the cursor back in the first one. After a failed sign-in, the email and password stay filled in. In September 2025 I also removed validation messages that pointed to the wrong field. [Figure: The verification step as designed in 2025.] ## Six boxes that behave like one field The code input looks trivial: six boxes, one digit each. On phones it broke. Android and iOS keyboards offer the whole emailed code as a single suggestion, and one tap put all six digits into the first box. The rest were lost. I fixed it in August 2025. Then I wrote down what the input has to do, however the code arrives: | Input | Result | |---|---| | A digit | Fill the box, move to the next | | Anything that isn't a digit | Ignored | | A full code, typed, pasted or autofilled into any box | Spread across all six | | Backspace in an empty box | Clear the previous box and move there | | Arrow keys | Move between boxes | | Six digits present | Submit once, and never while a request is running | Doing this once wasn't enough: all three apps had to follow the same rules, in two different code styles. Keeping them in step turned out to be a design task as much as an engineering one. ## 2026: putting the bank at the front door The Citizens Bank integration was a major feature, and it needed to be in front of every user. The existing login showed "What's New with Tigg" as a pale side column with two small post cards. In that layout, a bank partnership looked as important as a routine monthly update. The redesign was mainly visual. The sign-in form stayed where it was, on the right. On the left, the list became one featured post at hero scale: a dark, image-led panel with the date, title, excerpt, a "Learn More" button and previous/next controls. At launch in late May 2026, that post was "Citizens Bank International Now Integrated with Tigg". [Figure: The accounting login in 2025 and 2026. The form didn't change; the side column became one featured message. The numbered marks point to the parts of the featured panel: date and title, call to action, the link to all posts, and post navigation. The post title is the real announcement; the date is a placeholder.] The featured panel shows the newest post rather than a fixed banner, so it can be reused. When the next announcement comes, it's published as a post and the login page picks it up, with no new design or release needed. Signing in still had to come first. On a phone the form appears first and the announcement follows below, with its call to action pinned while the excerpt scrolls. If the posts fail to load, the panel shows "Unable to load the update." with a reload button, and the form beside it keeps working. I didn't redraw the verification, forgot-password and sign-up screens in 2026; only the login page changed. I also applied the same visual change to the companion account app. [Figure: The shipped login, desktop and phone.] ## Results and what I learned The 2025 verification flow was in Tigg's first tagged releases in February 2026. The 2026 redesign was merged to production on 27 May 2026 and included in a tagged release on 3 June. It looked more modern. Measured outcome: none. I have no data on sign-in success, resend rates, time to sign in, or clicks on the featured post. If I were measuring it, I'd log one event for each state change, including how the code was entered (typed, pasted or autofilled). I'd also track clicks on the featured post during a campaign. **The security feature is the recovery path.** Most of the work was in the states after something goes wrong: a wrong code, a late email, a lost field. **A small component can carry a contract.** Six boxes had to behave like one field for paste and autofill, in three codebases. **A visual redesign can do business work.** Making the bank integration the first thing every user sees didn't need a single flow change. ## States, edge cases and the first sign-in on a new device [Figure: The first sign-in on a new device. The device check and the email in transit are both invisible waits; each one has its own visual state. Backend behaviour isn't shown.] | State | What the person sees | |---|---| | Device not ready | "Preparing..." on a disabled button | | Required fields empty | Field messages under email and password | | Sign-in refused | The server's message under the form; typed values kept | | Code incomplete | "Please enter a complete 6-digit verification code" | | Code wrong or expired | The server's message; all boxes cleared; cursor in box 1 | | Resend cooling down | "Resend in 00:ss", red and blinking in the last five seconds | | Resend failed | "Failed to resend verification code" | | Unknown company address | "The company url … doesn't exist", with a way to claim it | | Subscription not verified | A message with the support contact | | Posts loading or failed | A spinner, or "Unable to load the update." with reload | Other edge cases I designed for: - surrounding spaces are trimmed from emails, and emails are lowercased so capitals don't cause a failed sign-in; - a deep link brings people back to where they were going after sign-in, and an unsafe link falls back to the home page; - after verification, an expired or first-time password still goes to the change-password screen. [Figure: The featured-post panel. It shows at most two posts, and whatever happens here, the form on the other half of the page isn't affected.] ## Built, then removed Several pieces of the 2025 work didn't survive in their first form. I count these removals as part of the design. | What | What happened | My reading, looking back | |---|---|---| | "Remember me" checkbox | Removed in August 2025 | Device trust replaced it with a decision the server enforces | | A trusted-devices page inside accounting settings | Built in August 2025, removed a month later, before any tagged release | Devices belong to the account, not to one app, so device management went to the companion account app. On the same day I extended the device list there | | "Remove all devices" | I hid it in August 2025; another engineer later brought it back with a loading state and disabled it when there's nothing to remove | | | A port of the accounting sign-in code into the POS | Abandoned after two exploratory attempts; a flow written in the POS's own style shipped instead | Fitting the POS codebase beat importing a second way of doing things | | A news column on the POS login | Built but never switched on, which saves a request on every sign-in | | In the account app, turning two-step verification on or off asks for the current password first. Trusted and untrusted devices are listed separately, and each can be removed after confirming. ### Notes on sources The dates come from the tickets and version history. The reasons in the right-hand column are my reading in hindsight, not notes from the time. The diagrams are redrawn from the shipped code and the Figma layer names, with fictional data. # Case study: Why a backup can't be a button (Tigg Accounting) URL: https://www.bibhushansaakha.com.np/work/backup Role: UI/UX design, all of the backup frontend. Not mine: The backup job, the email and the retention rule (backend). Team: Senior backend engineer, a frontend engineer (review and merge), QA. Period: Feb – Mar 2026. A company backup is requested, prepared, delivered and eventually expires. I designed and built the lifecycle so people can leave the page and still get their data. Key decision: The backup outlives the page: progress is resumable and the link also arrives by email. ## A button that would lie "Can I get my data out?" is a question every owner of an accounting product asks sooner or later. Tigg's answer is a full company backup: products, contacts, the chart of accounts, ledger transactions and stock movements, packaged by the server as a downloadable file. The obvious design is a "Backup Now" button with a spinner, and it would lie in ordinary situations. A full backup is a server job that runs in several parts and can take minutes. During that time the admin opens an invoice, reloads the page, switches to another organisation, or loses the connection for a moment. A button-and-spinner page forgets the job at each of those moments, even though the server keeps working. [Figure: Why a backup can't be a button. The server job keeps running whatever the admin does. A button-only page loses track of it at every ordinary interruption; the bottom lane shows what the shipped design does instead.] So I designed the backup as a lifecycle: requested, preparing, ready, downloaded, deleted or expired, and sometimes failed. The page is only one view of that lifecycle, and it doesn't own the job. ## Designing around leaving, not staying I designed the backup page and built all of its frontend, plus the public page that an emailed link opens, between February and March 2026. The backend team built the backup job itself, the email and the retention rule. Three decisions follow from "the backup outlives the page": - **The job is remembered.** When a backup starts, the browser saves a small pointer: which job, for which organisation. Coming back to the page, or reloading it, shows "Reconnecting to backup status…" and picks up where it left off. The pointer is kept only if the organisation matches. Each organisation has its own address, and an accountant who works for several clients shouldn't see one client's backup on another client's page. - **The link arrives by email too.** The backend emails a download link when the backup is ready, so nobody has to wait on the page. Small backups finish while the admin watches; large ones don't hold them there. - **One dropped check isn't a failure.** The page checks the job's status in the background. A single failed check is ignored; four in a row are tolerated; only the fifth ends the wait. The backup outlives the page. Progress can be resumed after leaving or reloading, and the finished file is reachable from the history table or from the emailed link, whichever comes first. [Figure: The backup page: one primary action, a progress panel that exists only while a job runs, the retention rule, and the history.] ## From a live stream to polling My first version, on 11 February, listened to a live status stream from the server, and each step of the job updated the message on screen. Within the hour I replaced the inline delete confirmation with a proper dialog and added the saved pointer, so a reload no longer lost the job. Two days later I added the email-link download page. Then the backend side ran into trouble. The senior backend engineer flagged problems with the live stream, so on 27 February I switched the page to asking for the status at intervals. On release day, 17 March, I tuned how often it asks: the checks settled at 3 seconds, then 10, then every 30. A small job reports back within seconds, and a long one costs only two requests a minute. [Figure: The backup page's states as shipped. Transient failures are hidden on purpose. Dashed states are the rarer endings, where a clearer message is the next refinement.] ## An honest word about the progress bar Polling made progress coarse. The server reports how many of its parts are done, usually seven. Between checks, the bar would sit still for up to 30 seconds and then jump, which looks broken. So the bar is partly estimated. It shows the larger of two numbers: the real share of parts completed, and a simulated value that creeps up in small random steps. The simulated value stops at a cap, and the real number is held at 99% until the server says the backup is complete, because the final step comes after the last part. At first the cap was 91%. It came down to 61% on release day, a change that also grew out of the backend problems the senior backend engineer raised. Looking back, the lower cap also stops the estimate promising "nearly done" while the job is still early. [Figure: Displayed, simulated and real progress for one assumed job. Until the real value passes the cap, the number on screen is simulated: in this example, for about the first 100 seconds. This is an illustration from the page's own timing rules, not a measurement.] I'm not fully comfortable with it. For most of a typical wait, the number is invented. The status messages cycle through a fixed set rather than following the real stage, so "Compressing data…" can appear before "Exporting products…". My first version tied each message to the part just completed, and I'd go back to that: show real stage names, keep the bar indeterminate until the server reports finer progress, and add a line saying it's safe to leave because the link will arrive by email. ## Ready, downloaded, deleted, expired When the job completes, the bar reaches 100%, a toast confirms it, and the new backup appears in the history table with its time, the person who started it, its status, and a download link. The download asks the server for a short-lived link at the moment of the click and then opens it. The emailed link opens a public page that works on a phone, because that's where emails often get read. It has four outcomes: the link is invalid, the download is being prepared, the link has expired, or "Download started". If the person isn't signed in, they're sent to sign in and then brought back to the same link. [Figure: The emailed-link flow. The page works without signing in until it needs to, then sends the person through sign-in and back to the same link.] Deleting a backup opens a confirmation that repeats which backup is going ("Backup from 12-03-2026 10:14:05"), because the rows look alike, and says it can't be undone. Failed backups have no delete action. On release day I added the retention rule right above the table: "Backups expire after 5 days and will be removed from this list." Rows that disappear without warning look like lost data. [Figure: The page an emailed link opens.] ## Results and what I learned The backup shipped in a beta release on 16 March 2026 and in a tagged release the next day, and it's in production. Measured outcome: none. I have no figures on how long backups take, how often people leave and come back, how often the page gives up while the server finishes anyway, or how many links expire unused. Those four numbers are what I'd instrument first. **Separate activity from measurement.** The simulated fill made the wait feel alive, but for most of it the number was invented. Showing that work is happening doesn't require claiming how much. **Design the long wait around leaving.** Resuming and the emailed link matter more than the bar, because they're what lets someone stop watching. **Name every ending.** A long job can end in more ways than done or failed: the page can lose contact, or the server can forget the job. Each ending deserves its own sentence for the admin, for example "We lost contact. Your backup may still finish; check the history." ## Iterations, edge cases and one neighbouring piece ### Five weeks, dated ### Edge cases and how the page handles them | Situation | What happens | |---|---| | Admin leaves or reloads mid-backup | The job continues; returning shows "Reconnecting to backup status…" | | Admin switches to another organisation | No resume there; the saved pointer is left for its own organisation | | One to four status checks fail | Nothing visible; checking continues | | Fifth check in a row fails | The wait ends with an error, though the job may still finish on the server (a gap) | | The server reports any part failed | "Backup failed" (or the server's message); no partial file is offered | | The server no longer knows the job | The page stops quietly (a gap) | | Repeated clicks on Backup Now | The button is disabled while a job runs | | No history yet | "No backup history available. Click "Backup Now" to create your first backup." | | Emailed link opened while signed out | Sign in, then back to the same link | | Emailed link too old | "This backup link has expired." with a way back to Tigg or to support | ### Nearby: who can see what, where Later, in June 2026, I also designed a new permission editor. It shows organisation-wide and branch-specific access as two labelled regions, each with a count. I also wrote copy that separates a switched-off feature from a missing permission: "This is an organization setting, not a permission on the user you are editing." A frontend teammate integrated it into the branch-wise accounting release. ### Notes on sources No ticket or written rationale survives for the backup. The dates come from the version history; the reasons for the stream and cap changes come from my own account of the senior backend engineer's concerns. Other reasons given here are my reading in hindsight. Tigg's public changelog lists the backup feature on 9 February 2026, two days before my frontend work began; I take that as the announcement date. The diagrams are redrawn from the shipped page with fictional data. # Case study: One release, not four dropdowns (Tigg Admin & ERP) URL: https://www.bibhushansaakha.com.np/work/admin-erp Role: UI/UX design, frontend. Not mine: Backend services; the original 2023 console and the earlier signup form. Team: CEO, backend engineers, a reviewing engineer, QA. Period: Jul 2025 – Aug 2026. Two ends of a Tigg tenant. In the admin console, a release tool the development team uses to test, deploy and run demos: one reviewed choice of release across selected workspaces. In the ERP portal, company creation rebuilt as five steps, with errors that find the person and Nepal-specific rules made correct. Key decision: Release is one reviewed batch action; the version control offers a single choice instead of four. ## Two tools at either end of a tenant Every Tigg customer is a tenant: its own workspace, its own features, and its own set of app versions. Two very different people touch that record. - **A business owner**, once, when they sign up and create their company in the ERP portal. - **The development team**, again and again, in the admin console, when they move workspaces to a new release to test it, deploy it or set up a demo. This case covers one piece of each. In the admin console I designed and built **Version Management**, the screen the team uses to move tenants between releases. In the ERP portal I rebuilt **company creation** as a guided flow and fixed the Nepal-specific details that made it fail in quiet ways. Both pieces are about the same thing: make the risky action narrow, and make errors impossible to miss. ## Version Management: one release, not four dropdowns Version Management was built for us, the development team. We needed one place to put a workspace on a new release to test it, roll it out, or prepare a demo. I built it over about three weeks in January and February 2026. ### The first version asked the wrong question Tigg ships several apps that are versioned separately: the accounting web app, the POS web app, a second POS product for a white-label partner, and the backend. My first version gave each one its own dropdown. The change only went through if all four pointed at the same release. If they didn't, you found out after pressing Apply. [Figure: The commit point before and after 9 February 2026. Left: four fields that must agree. Right: one choice of release, with its four component versions shown on each card. Reconstruction; workspaces and version numbers are fictional.] In retrospect, that design offered far more combinations than were valid. A release is a bundle: its four versions were tested together. Four dropdowns let you build a mix that never shipped, and the only guard was an error afterwards. On 9 February 2026 I replaced the four dropdowns with one choice of release. Each release is a card that lists the four versions it contains, so you can see what you are about to deploy before you commit. ### Guarding the one button that matters The modal has one commit point, Apply, and I put the guards around it: - The workspace's **current release is shown but disabled**, tagged "Current", so a no-op change can't be sent. - **Apply stays disabled** until the chosen release is different from the current one. - The **scope is written out** above the cards: "Sagar Stores is selected", "12 organizations selected" or "All organizations will be updated". - For a single workspace, the current release is **preselected** when all four versions match one release, so the starting point is visible and an accidental downgrade is harder. [Figure: The change-version modal as a state chart. Three entry points set the scope; one guarded Apply leads to submit; an error keeps you in the modal with your choice intact.] [Figure: The release picker as shipped.] ## What red and green should mean The version table colours each app version so the team can see at a glance who is on what. I got the meaning wrong the first time. - **12 February 2026:** only the latest release was marked, in green. Green meant "in sync with the newest". - **20 April 2026:** I added a second state. The latest release became **red** and the previous release, the *stable* one, became **green**. So this was not simply a colour swap. Green changed from "newest" to "proven", and the newest release became something to treat with care. In retrospect, that is the right reading for a team that uses the newest release to test before it reaches everyone. [Figure: How the colour meaning changed, and the fix I would make next: a word in every coloured cell, one colour pair shared by the table and the modal, and a legend in the banner. Workspaces and versions are fictional.] Reviewing it later, I found two problems of my own making. Colour is the only cue in the table, which fails for anyone who can't tell red from green. And the modal's "Latest" tag is still green, which contradicts the table. The fix is small: text tags ("Latest", "Stable") next to the colour, one shared colour pair, and a legend. ## Company creation: steps by task, not by data type On the other side of the tenant, a business owner creates their company in the ERP portal, often on a phone. Until July 2025 the form had three steps split by *type of data*: names and workspace address first, then accounting date, VAT, PAN and address, then logo, contact details and the terms. There was no choice of features, no review before submitting, and no clear moment of success. In July 2025 I rebuilt it as five steps, each answering one question: | Step | What the owner does | |---|---| | 1 · Details | Company name, industry, address, accounting start date, VAT registration, workspace name. Email, phone, PAN and website are folded into an optional "More information" block. | | 2 · Features | Seven cards, each with a plain description: Track Inventory, Manufacturing, POS Retail, POS Restaurant, Multiple Locations, Multiple Warehouses, Multi-Currency. | | 3 · Review | A summary of everything, plus the bot check, before anything is created. | | 4 · Submitting | A loading state; any problems the server finds are listed field by field. | | 5 · Finish | A short celebration and one button: "Go to my Organization". | Some features depend on others. Choosing either POS adds Multiple Locations; choosing Manufacturing or Multiple Warehouses adds Track Inventory. Turning a prerequisite off removes what depends on it. Getting these rules right took three revisions; the second one tied inventory to the wrong feature, and I corrected it the same morning. [Figure: The features step at phone width.] ### An error you cannot see Folding the optional fields away made the first step shorter, but created a new failure. If the PAN in the folded block was wrong, pressing Next did nothing visible. In November 2025 I made the error find the person, in four steps: 1. **Reveal** the folded block. 2. **Wait** 300 ms, the length of its opening animation, so the field is really on screen. 3. **Scroll** it to the centre of the view. 4. **Focus** it, so the keyboard lands in the right place. [Figure: The recovery sequence for an error inside a folded block. The wait matches the block's opening animation; scrolling before it finishes would stop short of the field.] ## Nepal details are correctness, not polish Two small fixes in the same flow mattered more than their size suggests. - **PAN is nine digits.** The form accepted 9 to 12. Nepal's Inland Revenue Department issues a nine-digit PAN, so in November 2025 I changed the rule to exactly nine. A wrong tax number is easy to type and hard to notice later. - **An empty date must stay empty.** The accounting start date uses a Bikram Sambat date picker. If you cleared the field and pressed Enter or Tab, the picker quietly filled in a default date again. That is worse than an error: it looks like your input. I fixed it in two passes (November 2025 and August 2026) so a cleared field closes and stays empty, and the "required" message can do its job. The default accounting start date is set to mid-July because Nepal's fiscal year starts on Shrawan 1. Right now that default is a fixed date that someone has to update each year. Working it out from today's date is on my list. ## Results and what I learned **Status.** Version Management (the list, the release picker, the add-release form, the colour rule and the filters) is in production, built between January and May 2026. The five-step company creation, the feature rules, the error reveal, the PAN rule and both date-picker fixes are in production, built between July 2025 and August 2026. The development team uses Version Management to test, deploy and run demos. **Measured outcome: none.** There is no record of how many workspaces were moved, of mistakes avoided, or of how many people finish company creation. If I measured one thing for each, it would be: - For releases: how often a bulk change is rolled back. - For company creation: where people leave the five steps, and how often a hidden error is revealed on Next. ### Lessons ## States and edge cases ### Version Management | Situation | What happens | |---|---| | One workspace whose four versions match no release | Nothing is preselected; Apply needs an explicit choice. | | The chosen release is the current one | The card is disabled and tagged "Current"; Apply stays off. | | Selected rows, then "Select all organizations" | A toast shows while every workspace is gathered; the link then reads "Deselect all" and restores the earlier selection. | | "Change All" | The scope line reads "All organizations will be updated". Apply is the only confirmation, which I would strengthen next with a typed or second confirmation. | | Save fails | An error toast; the modal stays open with the choice intact. | | Success | "Version updated", "Updated N organizations" or "All organizations updated", and the table refreshes. | | No matching workspaces | "No namespaces found"; pagination is hidden. | ### Creating a release The add-release form shows two columns, V2 (new, editable, prefilled from the latest release) and V1 (the previous release, read-only). When you change a field under V2, only that field's old value moves across to V1, with a short arrow animation and a "was …" label. Fields you don't touch keep their V1 values. The helper text says exactly this, because the rule is not obvious: the two slots are rewritten field by field, not appended. ### Company creation | Situation | What happens | |---|---| | Error inside the folded optional block | Reveal, wait, scroll, focus (chapter 04). | | PAN of 10 to 12 digits | Rejected; exactly nine digits are required. | | Cleared date, then Enter or Tab | The picker closes and the field stays empty. | | Workspace name already taken | Checked while typing ("Checking duplicate slug"). | | Turning off a prerequisite | Its dependent features are removed too. The same rules run when a saved draft is loaded, so a draft can't hold an impossible set. | | Server rejects a field on submit | The submitting step lists each field with its message. | ## How it was built, and notes on sources ### Building it I designed and built the frontend for both pieces. The backend services that store releases, move workspaces and create companies were built by backend engineers; a reviewing engineer merged most of my changes. The admin console itself dates from 2023 and was built by other engineers; the three-step company form and its bot check came from teammates before my rebuild. A few behaviours that shaped the experience: - **Filters and pages live in the address.** In the admin console, the version table's filters (by app version, subscription status and partner) and its page are part of the URL, so a filtered view survives a reload and can be shared with a teammate. - **"Change All" is one request.** An earlier version collected every workspace first and then sent the list. I removed that step, so updating everyone doesn't wait on gathering everyone. - **The release list is cached.** Releases change rarely, so the list of releases is kept for an hour, while the table of workspaces refreshes after every change. ### What I would do next - Text tags and a legend for release colours; one colour pair across table and modal. - A visible note when a feature prerequisite is added for you. - Work out the default accounting start date from today's Bikram Sambat date instead of a fixed value. - Put the logo upload back in the keyboard tab order on the details step. ### Sources This case is written from the shipped product and my own work history. There were no interviews or usability tests for these features, and no usage data. Where I give a reason that was not written down at the time, I say "in retrospect". Workspace names, version numbers and people in the figures are fictional. # Case study: Group settings by what they are (Tigg Accounting) URL: https://www.bibhushansaakha.com.np/work/configuration Role: UI/UX design and prototyping (in progress). Not mine: The brief and the six-group proposal (the CEO); the underlying settings screens. Team: CEO (brief and six-group proposal), engineers who own the settings screens. Period: Aug 2026 – now. Tigg's accounting settings were added on top of each other for years. The CEO wrote the brief and proposed a six-group structure; I'm designing it now, as a working prototype in the product. A work-in-progress case about information architecture, with its open questions and a test plan. Key decision: Group by what a setting is, not who uses it, so a setting has exactly one home. ## Where is the setting for that? This redesign is not released. The CEO wrote the brief and proposed the six-group structure; I am designing it now, as a working prototype inside the product. What follows is where the thinking stands in September 2026, including the questions I haven't answered yet. Tigg's accounting **Configuration** area decides how the rest of the product behaves: VAT account mapping, what happens when cash or stock would go negative, how documents are numbered each fiscal year, which banks and payment modes appear in forms, how invoices print, and how data comes in. People rarely visit it, but a wrong setting there quietly changes every invoice, bill and report after it. The structure that still ships has six top-level sections. One of them, **Apps**, holds 14 sub-pages that have little to do with each other. Inside Apps, a single **General** page stacks nine unrelated policy cards in one scroll: pricing rules, the inventory mode, batch and serial tracking, three balance policies and two VAT mappings. VAT rules sit next to printing templates. [Figure: The configuration that ships today: the Apps sub-menu and the General page.] ## Settings added on top of each other Nobody designed it this way. The page was built long ago, and as more features shipped, settings were simply added on top of one another without structure. The Apps list started with 11 entries in January 2021 and had 15 by March 2025, added and renamed by many engineers over four years. Each new feature went into whichever list its engineer was already editing. That produces three kinds of problem, and all of them are visible in the current screens: - **The label predicts nothing.** "Apps" says nothing about what is inside. - **Related things are split.** CRM lists live under "CRM", but CRM task types live under "Workflow". Feature switches are split between Organization and Apps › General. Opening balances and data import are separate top-level sections, although both are about loading data. - **The most consequential page gave no feedback.** The General page saved every choice instantly and silently. The only way to know a change had worked was to reload. The direction is simple to state: **group settings by functionality**, so that each group's name tells you what you will find in it. ## Two proposals, tried in the product ### The CEO's brief: group by department In June 2026 the CEO wrote the brief. It listed everything in the old structure, named the problem, and proposed groups by business area: Company & Billing, Financial Rules, Sales & CRM Setup, Purchase & Payments Setup, Documents & Templates, Automation, Data Management, Integrations and so on. In August 2026 I built that proposal as ten working groups inside the real product, on a development branch, so the grouping could be tried with real settings instead of on a diagram. Building it showed where it strained. **Some lists belong to more than one department.** Payment modes and banks are used by sales *and* purchases; task types are used by CRM and by everyone else. Filing each in one place hid it from half the people who need it, so three settings had to be listed in two groups. [Figure: The CEO's brief, my ten-group build of it (August 2026) and the six-group structure (September 2026). Red counts in the middle column are settings that had to appear in a second group.] ### The CEO's second proposal: group by what a setting is The CEO then made a six-group prototype: **Organization Setup, User & Permissions, Reference and List, Templates and Fields, Data Management, Integration and Automation.** Its groups are defined by the *kind of thing* a setting is, not by who uses it. In September 2026 I rebuilt the prototype on this structure, and this is the design I am developing now. [Figure: The same 36 settings sorted two ways. Sorting by who uses a setting needs three duplicates; sorting by what a setting is needs none. This is my own analytical sort, done while designing, not a study with participants.] When I sorted the 36 settings both ways, the reason for the difference was clear. A payment mode is a **list** whichever department uses it, so it has one natural home in "Reference and List". The department axis breaks exactly where lists are shared. ## The design I'm developing inside six groups A six-group outline leaves most of the design still to do. The prototype didn't place every setting, and the old pages weren't built to be moved. This is the work I am doing now. [Figure: The six-group structure as it stands in my working prototype: 35 settings. Dashed items appear only for some organisations; two integrations point out to the module where their settings already live.] ### Placing what the outline didn't cover - **Inventory Tracking Mode** and **Batch & Serial Tracking** go in Organization Setup. They weren't in the prototype, but they decide whether whole document types exist, so they belong with the settings that shape the organisation. - **Controls** is one page with three sections: pricing rules, balance and credit controls, and VAT account mapping. This is the old General page, taken apart by concern. - **Reference and List** is one flat group. There is no split by sales or purchase, and no setting is listed twice. - **Third Party Integration** gathers SMS and the marketplace connection in the hub, but links out to where their settings already live instead of moving them. ### Showing only what applies Seven settings depend on the organisation: an organisation without inventory doesn't see the inventory settings, and IRD sync appears only when IRD is enabled and verified. A person sees between 28 and 35 settings. Groups that would be empty are hidden, and each group opens on its first visible setting. ### Acknowledging every save Policy choices still save as soon as you pick them, but each card now shows a small "Saved" mark for about two seconds. If the save fails, the choice snaps back and an error explains why. A toast for every radio click would be noise; a quiet mark next to the card answers the question the old page left open: did it save? [Figure: The Controls page in the working prototype.] ### Moving the doors, not the rooms Configuration has dozens of screens, and other parts of the product link straight into them. So the change is navigational: each setting keeps its existing screen, and I'm adding redirects from the old addresses to the new homes as I go. That is also what let the grouping be tried twice in six weeks. ## Open questions, and how I'd test them These are the questions I can't settle by reasoning alone: | Question | Why it's open | |---|---| | Should tax live in one place? | VAT mapping is in Controls, TDS types in Reference and List, IRD sync in Integration. Each placement follows the rule; together they scatter tax. | | Is "Reference and List" too long? | Up to 14 items in one column for manufacturing organisations. | | Does Production Order Status need a home of its own? | It fits nowhere well. An Inventory & Production group may be warranted. | | Six groups, or seven? | The grouping is still being tried and is not final. | | Do people expect search? | Settings search isn't built. It may matter more than the grouping. | ### Validation plan (not yet run) 1. **Open card sort** of the 35 settings, to see whether people group them by what they are or by department. This tests the main idea directly. 2. **Tree test** of the old structure, the ten-group version and the six-group version, with 10 to 12 tasks such as "Stop invoices when a customer is over their credit limit", "Add a new bank" or "Change how quotation numbers restart each fiscal year". Measures: task success, first click correct, time. 3. **Who:** Tigg support and QA first, because they are easy to reach; then five to eight customer admins. ## Where it stands **Status: work in progress.** The six-group prototype runs inside the product on a development branch. It is not merged, not released and has not been through QA. There has been no user testing and no feedback on it yet, so there is nothing to measure. **Measured outcome: none.** After release, I would watch "where is setting X?" questions to support before and after, and how often old addresses are still used. ### What I've learned so far ## Where each setting moves, and its states ### A sample of the mapping | Setting | Today | Ten groups (Aug 2026) | Six groups (Sep 2026, in progress) | |---|---|---|---| | Negative Cash / Item Balance, Credit Limit | Apps › General | Financial Rules | Organization Setup › Controls | | VAT on Sales / Purchase | Apps › General | Financial Rules | Organization Setup › Controls | | Inventory Tracking Mode | Apps › General | Features & Options | Organization Setup | | Payment Modes, Banks | Apps | Purchase & Payments **and** Financial Rules | Reference and List | | Task Types | Apps › Workflow | Automation **and** Sales & CRM | Reference and List | | Quotation, Sales Order, Cheque statuses | Apps › Custom Status (one page) | split by department | Reference and List (one each) | | Printing Templates, Document Numbering | Apps | Documents & Templates | Templates and Fields | | Opening Balances, Data Import, Backup | three places | Data Management | Data Management | | IRD Sync, Developer API | Organization | Integrations | Integration and Automation | ### Decisions inside the build - **Features stays one setting.** Billing locations, multiple warehouses and multiple currency share one screen that can't be split cheaply. Splitting it is a follow-up, not a navigation change. - **Custom Templates stays one list.** The prototype showed letter, email, SMS and terms templates as separate cards, but they are one list filtered by type, so one entry is honest. - **Task types stay together.** The brief asked to separate CRM and non-CRM task types; the records have no field to tell them apart, so that has to come first. - **User & Permissions keeps its own three tabs** (users, roles, invited users) and its address, because the permission editor depends on it. ### States of a policy card | State | What the person sees | |---|---| | Loading | A loader in place of the cards. | | Could not load | An error with a Reload button. | | Ready | The current choice selected. | | Saving | The new choice is shown at once. | | Saved | A "Saved" mark beside the card for about two seconds, announced politely to screen readers. | | Save failed | The choice returns to its old value, with an error message. | ### Not designed yet - A group-level "you don't have access" state; for now each page relies on its own permission checks. - A warning when leaving a list row with unsaved edits. - A layout for narrow screens. The group page assumes a desktop, which is where Tigg Accounting is used today. ## Credit and sources **Who did what.** The CEO wrote the brief, including the inventory of the old structure and the first grouping, and made the six-group prototype. I built the ten-group version from the brief, am building the six-group design, decided where the settings the outlines didn't cover should go, split the General page and added save feedback. The settings screens themselves were built by many engineers since 2021. **Sources.** Counts come from the product and its history: the Apps list over time, the settings in each version, and how many each organisation sees. The sorting in the second figure is my own analysis, done while designing. There were no interviews, card sorts or tree tests yet; the plan in chapter 05 is what I would run before release. # Case study: The screen is not the end (Tigg POS) URL: https://www.bibhushansaakha.com.np/work/pos-operations Role: UI/UX design, frontend. Not mine: The QR payment modal and its integration (senior frontend engineer); the NiziPOS device (Yarsa Tech). Team: CEO, senior frontend engineer, QA. Period: Dec 2025 – Sep 2026. Three short cases from Tigg POS, each a state that leaves the browser tab: returning to the order after Pay Now, telling a barcode scan from typing, and the dynamic-QR customer display on NiziPOS, a separate device by Yarsa Tech. Key decision: Design the hand-off, not just the screen: the return path, the scanner and the customer display each get explicit states. ## The screen is not the end A POS screen is where a cashier works, but most of what matters happens *after* the cashier looks away. The customer pays at a device on the counter. The scanner keeps typing into the page. The cashier needs to land back where the next order is waiting. This case collects three small pieces of Tigg POS, the till used by Nepali shops and restaurants. Each one is a state that leaves the browser tab: | | The hand-off | What goes wrong if the state is wrong | |---|---|---| | **A** | From payment back to the queue | The cashier is dropped on the home screen and has to find their place again, after every bill. | | **B** | From a barcode scanner into the page | A scan adds the previous item again, or two scans run together into one code. | | **C** | From the till to a customer-facing display | The customer sees a stale QR, or nothing, and asks "did it go through?" | In all three I tried to follow one rule: **design the state, not just the screen.** A state has to hold up after the person has stopped looking at it. ## A · Returning to the order after Pay Now In a busy restaurant, the floor lead collects payment from the order list or the kitchen ticket (KOT) queue. Until December 2025, payment always ended on the location's home screen. Whoever started from the queue had to find it again, bill after bill. The fix sounds trivial: go back. It took me four tries in one week, and two of them were reverted. | Try | Idea | Why it failed or held | |---|---|---| | 1 | A yes/no flag: "came from the order list" | It named one origin only. The KOT queue and retail orders were next, and a yes/no can't name three places. | | 2 | Use the browser's Back | In a POS, "back" is unpredictable: print dialogs, reloads and links opened directly all change what it means. | | 3 | Back, but only when the origin is the order list | Still history underneath, so the same problem returned behind a condition. | | 4 | **Name the destination** | The screen that opens payment says where it came from. Payment returns there. | The origin is stated explicitly (order list, KOT list or retail orders) instead of guessed from browser history. It survives a reload or a link opened directly, and adding a new origin means adding one name. [Figure: Four origins, one payment page, four exits that all follow the same return rule. Reconstruction from the product's behaviour.] The return fires at every way out of payment: an invoice or credit note saved without printing, or the bill or credit-note print dialog closed. Retail orders can be reached in several ways, so they go back in history but fall back to the retail order list if there is nothing to go back to. A payment started from the order page itself still ends on the home screen, as before. ## B · Telling a barcode scan from typing Most shop scanners behave like a keyboard: they type the code very fast and press Enter. To the order page's search box, a scan and a person typing look the same, unless the design teaches it the difference. In May 2026 I designed the barcode-mode order page for web and the mobile app. In August 2026 I rewrote how the page tells a scan from typing, because of three failures: - The scanner's Enter arrived before the search had caught up, so the page acted on the previous result and **added the previous product again**. - Two quick scans landed in one box and **ran together** into one long code. - When a field was focused, both the field and the page-wide scan listener **handled the same keystrokes**. ### Pace, not a streak My first rule, on 4 August, looked for a streak of fast keystrokes. Two days later I replaced it with an average. A string counts as a scan if it has at least four characters and arrives at 60 ms per character or faster, on average, from the first character. One slow gap, such as a hiccup on the USB cable, breaks a streak but hardly moves an average. [Figure: The rule as geometry. A string is scan-like if its trace ends inside the blue zone. The two traces are illustrative, not measurements; they show why an average survives the single slow gap that broke the earlier streak rule.] Detection decides only one thing: whether to clear the box. A scan clears at once, matched or not, so the next scan can never land on top of it. A typed search that finds nothing stays in the box, so the person can keep refining it. The lookup itself is the same for both. If a very fast typist is mistaken for a scanner, the worst outcome is a cleared box and a "No barcode" message, never a wrong item on the bill. After the lookup there are three outcomes. No match shows a "No barcode" message. One match is added, or opens the amount keypad when the shop bills by amount. **More than one match opens a chooser** instead of silently taking the first, which turns a hidden error into a visible choice. Every scanner we tested with works with this rule. ## C · The dynamic-QR customer display on NiziPOS In many Nepali shops a digital payment starts with a printed QR on a stand: the customer scans it and types the amount. A **dynamic QR** carries the amount for one bill, so there is nothing to type. The question is where the customer sees it. [NiziPOS](https://www.yarsa.tech/products/NIZIPOS), made by Yarsa Tech, is a separate small device that faces the customer and shows the dynamic QR. Tigg POS drives it from the cashier's browser over USB. ### Who did what I designed the overall dynamic-QR payment flow for the app and web. The senior frontend engineer built the QR payment itself: the Fonepay and NepalPay integration, the countdown and the live payment status. For the display, I designed its screens (June 2026) and built the browser side that connects to the device and tells it what to show (July to September 2026). ### Five screens for the customer The customer should see the same moment the cashier sees: [Figure: Top: the five customer screens. The QR screen carries the amount, so the customer doesn't type it. Bottom: the cashier's QR modal. When the display is connected, no control appears; when it isn't, one small 'Connect POS display' icon appears. Reconstruction; the amount is fictional.] ### Designing the connection, not just the screens A USB display is not a second browser window. The page has to find the device, get the browser's permission once, stay connected across reloads and replugs, and never let a missing display block a payment. I designed it as two linked state machines: one for the **connection**, one for what the **display** shows. The display only changes while the connection is up; otherwise its updates are skipped and the payment carries on. [Figure: The connection machine (top) and the display machine (bottom). The numbered and lettered transitions are explained in the deep-dive chapter. Reconstruction; the device's own command format is left out.] ### From a permanent button to a fallback The "connect" control had three homes in ten days in July 2026: first a button in the header, then an icon inside the QR modal, and finally only a fallback. The browser needs a click only for the first permission. After that the page reconnects by itself when it loads or when the device is plugged in. What is left for the cashier is one icon, in the one place the display matters, shown only when it is not connected. In retrospect, each move took away a reason for the cashier to think about the device. ## Results and what I learned **Status.** All three pieces are in tagged releases of Tigg POS: the Pay Now return path in the first POS release of February 2026, the scan detection in August 2026, and the NiziPOS display in September 2026. Every scanner we tested works with the new detection. **Measured outcome: none.** I don't have numbers for extra navigation after payment, double-added scans or QR payments completed on the display. If I measured one thing for each piece: | Piece | What I would measure | |---|---| | A · Return path | Extra navigations per payment when paying five orders from the KOT queue (target: none). | | B · Scanning | How often scans and typing are misclassified, with two or three scanner models and a typist baseline; double-adds per 100 scans. | | C · Display | Recovery without a page reload after unplugging, reloading, opening a second tab and cancelling the permission prompt. | ### Lessons ## Edge cases and states ### A · Return path | Situation | What happens | |---|---| | Payment opened from the order list or KOT list | Returns to that list at every exit from payment. | | Payment opened from retail order cards | Goes back; if there is no history (a link opened directly), falls back to retail orders. | | Payment opened from the order page | Ends on the location home screen, as before. | | The page is reloaded mid-payment | The origin is part of the address, so the return still works. | ### B · Scanning | Situation | What happens | |---|---| | Scan, one match | Added to the bill; the box clears. | | Scan, one match, shop bills by amount | Opens the amount keypad (for example "Rs 500 of tomatoes"). | | Scan, several matches | A chooser opens; nothing is added silently. | | Scan, no match | The box clears and "No barcode" appears. | | Typed search, no match | The text stays so the person can keep typing. | | Two scans in quick succession | The first clears before the second lands, so they can't run together. | | A field is focused | That field owns its keystrokes; the page-wide listener ignores them. | ### C · NiziPOS connection and display | # | Transition | Why it's designed this way | |---|---|---| | 1 | Open a device permitted before, on page load or when it's plugged in; or open from a click on "Connect POS display" | The browser needs a click only for the first permission; after that, reconnecting is silent. | | 2 | Opening fails because an earlier session still holds the device: close it and retry | Otherwise a working display looks dead until someone reloads. | | 3–4 | Opened → Connected; a failed update or an unplug → Disconnected | The state follows the device, not what the screen last assumed. | | 5–6 | Permission prompt cancelled or failed → no automatic prompt until the device is next unplugged | A cashier who dismissed it shouldn't see it on every payment. | | a–d | QR chosen and generated → QR screen; checking → "Processing..."; result → "Payment Successful" or "Payment Failed" | The customer sees the same moment the cashier sees, with the amount on the QR. | | e | Leaving payment or finishing → Idle, sent only when the page really is idle | An idle screen sent at the wrong moment can wipe a result the customer is still reading. This was tightened after QA in late July 2026. | In browsers that can't reach USB devices, the display is simply skipped and payment works as normal. ## Trade-offs, credit and sources ### Trade-offs I accepted - **Checking instead of listening.** The payment screen checks whether the display is connected every 1.5 seconds rather than waiting for the device to announce it. It is simple and hard to break, at the cost of up to a second and a half of lag in the icon. - **Browser support.** Talking to a USB device from the page works only in Chromium-based browsers on a secure connection. Elsewhere the display is skipped, so payment never depends on it. - **The display shows only the payment moment.** Some POS products mirror the whole cart on a second screen. This one shows the amount, the QR and the result, which is what the customer needs to pay. ### Credit - The kitchen queue, the barcode feature and the original scan listener existed before me; I redesigned and extended them. - The QR payment modal, its provider integration, countdown and live status were built by the senior frontend engineer from my flow design. - NiziPOS is a product of Yarsa Tech. I designed its screens for Tigg and built the connection from the POS. - The senior frontend engineer reviewed and merged most of this work; QA tested it before release. ### Sources Written from the product, my design tickets and my own work history. There were no interviews or usability tests for these pieces, and no usage data. Reasons marked "in retrospect" were not written down at the time. The typing traces in the scanner figure are illustrative, not measurements. # Case study: Learning with help, practising without it (Personal) URL: https://www.bibhushansaakha.com.np/work/kati-sajilo Role: Design and build (solo). Team: Solo. Period: Jan 2026. A Nepal Engineering Council licence-exam prep app I built in four days for myself and my friends. One question bank, three levels of help: Learn explains every answer, Practice helps when asked or wrong, and a timed test gives no help until the review. Key decision: Help is a mode, not a button: Learn explains even right answers, Practice helps on request or when wrong, and a timed test holds everything back until the review. ## One question bank, two different jobs In January 2026 I was preparing for the Nepal Engineering Council licence exam, the multiple-choice paper an engineering graduate in Nepal has to pass to register and practise. My friends were preparing for it too. We had question sets from many places, mostly documents and PDFs with the answers printed beside the questions. Reading a set like that does two jobs badly. - **Learning.** You see the answer before you commit. If you guess right, you never read why it was right, so a lucky guess teaches nothing. - **Rehearsal.** Nothing tests recall under a clock. The exam is two hours long, and speed of recall matters as much as recognition. Get the balance wrong and you pay for it either way. Help that is always on gives false confidence. No help at all means a first pass through a half-forgotten chapter produces no learning. I built Kati Sajilo ("how easy", in Nepali) over four days, 6 to 9 January 2026, to speed up our preparation. It is a small React web app with one question bank, and it lets you choose how much help you get with it. [Figure: The problem as one axis. Printed answer keys sit at one end, a bare mock test at the other, and real study needs the points in between. Conceptual diagram.] ## Who it was for, and where the questions came from I was the user. There were no interviews or usability tests. The research was my own preparation and a group of friends preparing alongside me. Many of them used it, but I have no usage numbers, and I won't guess at them. Three things about the exam shaped the design: - **Several sittings a year.** A friend group rarely sits on the same date, so a friend who has just sat the exam is a useful source for the friends sitting next. - **Preparation lasts weeks.** Progress has to be there when you come back tomorrow, on the same phone or laptop. - **Nobody signs up for a friend's side project.** There is no login. History is kept in the browser's local storage. The questions come from many sources, which I combined into one bank: chapter-wise sets, official model questions, past questions, and 55 questions I recalled myself. I memorised what I could during my own sitting and wrote the questions down afterwards. Those went to the top of the home page, marked highest priority, for friends sitting later. It is a study tool, not an exam replica. The Full Test is a 100-question, two-hour simulation. It does not weight two-mark questions the way the official scheme does. I don't claim it improved anyone's result. ## Help is a mode, not a button The obvious design is a quiz with a hint button. I started there, and it became the middle mode. What I ended up with is three modes over the same questions, each a different contract about what you are allowed to know and when: | | Learn | Practice | Timed test | |---|---|---|---| | Question order | In order, by chapter | Shuffled | Shuffled, spread across chapters | | Hint before answering | None | When you ask | None | | Right or wrong | Shown at once | Shown at once | Not until the review | | Explanation | Always, even after a right answer | Opens on a wrong answer | Deferred to the review | | Change your answer | No, it locks | No, it locks | Yes, until Finish | | Clock | Time spent | Time spent | Countdown, red under five minutes | Read left to right, help goes down and commitment goes up. Every timed test ends in a review, where the help returns for each question. Retaking a test gives you the same paper again, so two attempts can be compared. The rule I care most about is in Learn: the explanation opens even when you are right. That is the case the answer key never covers, a correct guess you don't understand. [Interactive step-by-step example] [Figure: The assistance gradient: three modes against each dimension of help. Highlighted cells are the rules that make each mode what it is. The persona labels are assumption-based framings, not interviewed people.] [Figure: Learn keeps the explanation open even after a right answer.] ## I added a database, then removed it the same evening The first version, on 6 January, already kept attempt history in local storage. On the second evening I added a hosted database, so that history would live on a server as well. I had no experience with backend databases. Most of that evening went into getting the database to set itself up on the host, not into anything a friend preparing for the exam would see. So I removed it the same evening and went back to local storage. Keep study history in the browser, with no account. Questions are still served by the app; only the record of your attempts stays on your device. What that choice gives and costs: | Gives | Costs | |---|---| | No setup, no sign-up, nothing for friends to create | History is per browser: no sync between phone and laptop | | Nothing to run or maintain for a four-day build | Clearing the browser clears your history | | A test stores its full paper, so review and same-paper retake work without a server | Storage is small, and a save that fails does not tell you yet | The practical lesson was simple. A piece of infrastructure has to earn its place in the product. Two hours of setup work had produced no feature. Removing it made the app simpler, and the decision became visible in the product as "No login required". ## What happens when you switch tabs? A timed test is only a rehearsal if it can't be paused for free. On a phone, though, a notification, a call or a quick app switch is normal. So the question is: what should happen when the test tab is hidden? When I read the code again in 2026, I found that it answers that question twice, in two different ways: - **The timer is built to survive.** When you come back, it recalculates the time left from the real clock, so time spent away still counts. - **The test page is built to end.** When the tab is hidden it asks for confirmation, and if the window has no focus it ends the test straight away and goes to the review. One part prepares to resume and the other ends the attempt. In practice the page usually wins. This comes from reading the code, not from a reproduced bug, and it still needs a test on real phones. But it is an unresolved policy, and I'd rather decide it than leave two answers in place. [Figure: Timed-test lifecycle. The three exits from Running are Finish, time running out and the tab being hidden. The third behaves differently from the other two. Reconstruction from the app's behaviour.] [Figure: Leave-policy options. My recommendation is (b) with (d): keep the clock running, warn on return and show the number of leaves in the review. Options analysis done in 2026, after the build.] Option (d) turns leaving into information rather than punishment. A real exam doesn't stop its clock, and a rehearsal shouldn't either. But an interrupted phone session shouldn't throw away an hour of answers. ## What happened, and what I learned **Status:** a personal tool. I used it for my own preparation, and many friends used it for theirs. It is live at [necmcq.vercel.app](https://necmcq.vercel.app). **Measured outcome: none.** I have no counts of users, sessions or scores. I don't know whether it changed anyone's result, and I don't claim it did. What the four days did show is how priorities moved as the exam came closer. Chapter practice came first, then timed tests and review, then Learn mode, then the official and past sets. My recalled questions were added last, after my own sitting. The home page was reordered to match: recalled questions first, official and past questions second, chapter study after that. [Figure: The home page, ordered by how close the exam is rather than by chapter.] Three lessons I took from it: 1. **Decide what the learner may know, and when, before styling a screen.** The three-mode contract is the product. The screens follow from it. 2. **Infrastructure has to earn its place.** The database added setup work and no feature. Local storage was simpler, and simple enough to ship before the exam. 3. **Every exit is a design decision.** Finish, time running out and leaving the tab should produce the same kind of record. Writing the rule down once would have made the conflict obvious. ## States and edge cases ### The same question on three screens [Figure: One fictional question in Learn after answering, in a running timed test, and in the review, at phone width. Reconstruction, fictional question and data.] [Figure: The timed test: a numbered navigator with three cell states (current, answered, not answered). On phones it becomes a drawer that closes itself when you pick an answer.] [Figure: The review returns the hint and explanation for every question, and offers Retake on the same paper.] ### States that exist - **Loading:** a spinner with "Loading questions…" or "Loading exam…". - **Error in Learn:** an error card with Retry and Back to Home. - **Empty:** "No questions available for chapter N", "Question not found", and in the review, "Session not found" with a way back to attempts. - **Confirmations:** before a retake ("This will start a new session"), before leaving a running test, and the browser's own prompt before closing the tab. - **Denied:** none, because there are no accounts. ### What I found reading the code again (not yet reproduced) In September 2026 I read the source again for this write-up. These are findings from reading, not bugs anyone reported, and I'd confirm each one in a browser before calling it reproduced. | Finding | Why it matters | Fix | |---|---|---| | The three exits record the test differently. Finish and time-out count unanswered questions as wrong; leaving the tab leaves them "not attempted" | The same paper gives differently shaped reviews depending on how it ended | One way to complete a test, with the reason stored | | Question numbers restart in each chapter, and an attempt is matched by number alone | In a mixed test, two different "question 12"s can overwrite each other's attempt | Identify a question by chapter and number together | | A failed save to storage is not shown | You could lose history without knowing | A visible notice, and an export of your history as a file | | One chapter's Learn set sat outside the list of chapters Learn shows | 50 questions were unreachable in Learn | Build the chapter list from the data | | If a test fails to load, the spinner stays | No way forward except reloading | An error state like the one Learn already has | ### Accessibility All core actions work from the keyboard (number keys to pick, arrows to move, a key each for hint, explanation and next), and navigator cells have labels. Still to do: right and wrong are shown by colour alone, the phone drawer does not trap focus, and the countdown isn't announced to screen readers. ## The question bank, and what I'd test next ### Combining sources The sets came in different shapes. Some files were one list of questions, some were grouped by chapter, and a few were long enough to be split in two. The loader accepts each shape, skips a question that is missing its text, options or answer, and repairs common formatting damage from copying out of documents, such as curly quotes and stray line breaks. That repair makes a file readable. It does not make it correct. A readable question can still have a wrong key, a weak explanation or a place in the wrong chapter. Checking that is a separate job, and the app doesn't do it yet. ### What I'd test next 1. **Answer-key check.** Compare a sample of each set against the official model answers, and log corrections per set. 2. **Mode comprehension.** Five engineering graduates, think-aloud. Before answering, can they predict what each mode will show them? That tests whether the contract is clear. 3. **Interruption test.** On Android and iPhone, trigger a notification, an app switch and a screen lock during a short test. Record which exit happens and what the review shows. The result decides the leave policy. 4. **Record integrity.** Answer all 100 questions in a full test and check that the review counts 100. Next changes, in order: choose and write down the leave policy; identify questions by chapter and number; add an optional weighted "official format" test; allow exporting and importing history as a file, as the no-account answer to sync. ## Notes on sources - Dates, modes and rules come from the app's source and its history (6 to 9 January 2026). - Use by friends is my own account. I have no usage figures. - Exam format details come from public guides and the council's published scheme, which differ on the split of one- and two-mark questions. That is why I describe the Full Test as a simulation, not a replica. - The recalled questions are my own. I wrote them down after my sitting. - All questions shown on this page are fictional. - Screenshots will be taken from the live site at necmcq.vercel.app. # Case study: Showing the pattern, hiding the number (Personal) URL: https://www.bibhushansaakha.com.np/work/dime Role: Design and build (solo). Team: Solo. Period: May 2026. A personal finance visualiser. I added a mask so I could share charts without showing how much money I have, then found places where amounts still leaked through. Key decision: Privacy is a display mode that has to cover every surface, including chart axes. ## I wanted to share the chart, not the balance Dime is an open-source iPhone expense tracker, made by someone else. Logging a purchase takes seconds, and that is what a phone is good at. Reading patterns across months is harder on a small screen: which categories drift, when in the week I spend, whether a month was unusual. In May 2026 I built a desktop-browser companion for my own use. It reads the CSV file that Dime exports and turns it into ten views of the same transactions. I call it Dime Insights here. No one else uses it. Two days after the first version, I added one switch to the sidebar: **Hide amounts**. I wanted to show the charts, on a shared screen or in a portfolio like this one, without showing how much money I have. This case is about that switch. It is also about the places it missed, which I found later. [Figure: The Overview with amounts hidden. Synthetic data.] ## One file in, ten views out The importer expects exactly one format: Dime's own CSV export, with five columns (date, note, amount, category and type). It is not a general bank-statement reader. The upload screen shows the expected format before you drop the file, so the contract is visible up front. Transactions are saved in this browser's local storage, so a reload brings the dashboard back. There is no account and no sync. I haven't done a network audit, so I don't promise more than that: the data is kept in the browser. Some details that matter for my use: - **Rupees, grouped the Nepali way.** Amounts print as Rs with lakh grouping (Rs 1,00,000), the format I read without thinking. - **Desktop only.** A fixed sidebar holds navigation and every global control. It is not designed for phones; the phone already has Dime. - **Plain methods.** The forecast is a straight trend line over past months, labelled as linear regression. [Figure: Context. The export file is the whole integration: the phone captures, the browser explains, and transactions are kept in this browser. Diagram from the app's behaviour.] ## What a viewer should still be able to learn "Hide amounts" can mean several things. I compared three: | Option | What the viewer learns | Problem | |---|---|---| | A. Blur the whole screen | Nothing | Nothing left to discuss | | B. Hide the balance only | Most amounts, through labels and axes | Leaks through everything else | | C. Replace every amount, keep everything else | The pattern only | Hard to make complete | I chose C. With the switch on, every formatted amount becomes a fixed placeholder, "———". Chart shapes, percentages, counts and dates stay. You can still see that May was 16% lower than April, which category is the largest, and which day of the week is expensive. You can't see what any of it adds up to. [Figure: Masking alternatives. Option C keeps what is worth discussing and removes what is private. It is also the hardest to get complete, because every place that prints money must go through it. Reconstruction, synthetic data.] Two small choices made C work: - **A fixed-width placeholder.** Three dashes for every amount, so the mask doesn't reveal whether a number has two digits or six. - **One formatter behind the switch.** Money shown in a stat or a tooltip goes through one shared formatter that checks the switch. Adding the mask did not mean touching every chart separately. Hiding is a display mode over the whole app, not a blur over one card. Shapes, percentages, counts and dates stay readable; every amount goes. ## Where the mask missed The sidebar said "Amounts hidden". But not every amount went through that formatter. When I went back through the app for this write-up, I found that the first version of the mask missed several places: - **Chart axes.** Seventeen Y-axis scales across seven sections built their own "Rs.20k"-style labels instead of using the shared formatter. With the mask on, a viewer could still read the scale off the side of the chart, and from the scale, roughly how big every bar is. - **Generated sentences.** The insight cards put amounts straight into their sentences, before the mask can see them. - **Bucket labels and what-if figures.** The spending distribution labels its ranges in rupees, and the what-if forecast prints its difference as a rupee string. [Figure: The mask, off and on. (1) Stat values become dashes. (2) Tooltips are masked too. (3) The Y-axis still prints rupee ticks, because those labels were built separately. (4) Percentages, counts and dates stay, by design. Reconstruction, synthetic data.] [Figure: The leak as it appeared: dashes in the stats, rupees on the axis. Synthetic data.] The mechanism was right. The coverage was not, because formatting money wasn't in one place yet. Some parts of the app formatted amounts on their own, and a switch can only hide what passes through it. A second gap is quieter. The switch resets to off when the page reloads, so opening the app on a shared screen shows the numbers first. A privacy feature is only as good as its least obvious surface: an axis tick, a sentence, a label. The fix isn't more care in each chart. It is one formatter for values, axes and text, and a check of every screen with the switch on. ## The Pro page was a test The app has a Pricing page, three Pro-only sections and a few blurred Pro cards. None of it takes payments. There is no checkout, the plan buttons do nothing, and a "Test Pro" switch in the sidebar unlocks everything. I built it to try out how the features would be packaged, not to sell anything. It taught me one thing worth keeping: a blurred card is not concealment, because the real chart is still drawn underneath. That doesn't matter for a test gate. It would matter for anything private, which is one more reason the amount mask replaces text instead of blurring it. ## Status and what I learned **Status:** a personal tool, used only by me. All data on this page is synthetic. **Measured outcome: none.** There are no other users and nothing to measure beyond my own use. A screen-by-screen check with the switch on is the first test in the plan below. Three lessons: 1. **Privacy has to cover every surface.** Values, tooltips, axes, generated sentences and labels all print money. A mask that covers most of them tells the viewer something false: that the screen is safe. 2. **A privacy default should survive a reload.** If the safe state resets, the unsafe state is what a shared screen shows first. 3. **State the contract, and hold the file to it.** The upload screen tells you which export it expects. The importer should be just as strict, and show what it couldn't read instead of guessing. ## States, and what the importer gets wrong [Figure: The app's states. The dashed state is the one you cannot see: the dashboard works, but a failed save means a reload returns to the upload screen. The toggles run in parallel, and only the theme survives a reload. Reconstruction from the app's behaviour.] [Figure: The upload screen shows the expected export format before you drop anything.] ### What I found reading the code again (not yet reproduced) The masking gaps in chapter 04 are the ones I'm sure of. Reading the importer again turned up more. I haven't reproduced these in a running browser, so I list them as findings to test, with the fix I'd make. | Finding | Consequence | Fix | |---|---|---| | A date the importer can't read becomes today's date | A broken row can land in "last 30 days" and look like a real, recent purchase | Put unreadable rows in a "needs review" list, never into the charts | | Dime exports times in UTC; the importer reads them as local time | In Nepal (UTC+5:45), late-night purchases can shift to the previous day | Read the export as UTC and show it in local time | | Only an exact "Income" counts as income | A lower-case "income" would be counted as spending | Read the type case-insensitively and flag unknown values | | An empty category shows as a blank name | A nameless slice appears in the category charts | Show it as "Uncategorized" | | "Change file" deletes stored data without asking | A mislabelled, destructive action | Rename it "Remove data…" with a confirm step | | A failed save is silent | The dashboard looks saved but isn't | A visible notice | The first one taught me the most. Forgiving input keeps the dashboard from ever crashing, but in a finance tool a plausible wrong date is worse than an error. [Figure: Proposed, not built: an import review that makes excluded rows a visible number instead of a silent guess.] ## How I'd check it, and notes on sources ### Validation plan 1. **Masking audit.** Turn the switch on and capture all ten sections with the synthetic sample file. Pass means no readable amount on any screen: stats, tooltips, axes, sentences and labels. Target: zero. 2. **Importer tests.** Small fictional files covering each case above: good dates, broken dates, missing columns, lower-case types, empty categories, negative amounts. 3. **Time check.** Log a purchase at a known time after midnight in Nepal, export it, and confirm the day and hour the app shows. 4. **Cross-browser check.** Load the same file in Chrome, Firefox and Safari and compare how many rows each one reads. ### Fix order One formatter for values, axes and generated text comes first, because it closes the whole class of masking gaps. Then keep the switch's state across reloads, and consider making hidden the default. After that, the import review. ### Notes on sources - Dates, behaviour and the masking gaps come from the app's source and history (built 23 May 2026, mask added 25 May 2026). - "Hide amounts" was added so I could share charts without revealing how much money I have. That is my own account. - The Pricing page and Pro tier were a test. There were never payments, accounts or other users. - All figures and screenshots use synthetic data. None of my own transactions appear on this page. # Paper: Who may see my cycle? Consent-first sharing in menstrual and reproductive health tracking: a survey of young adults in Nepal URL: https://www.bibhushansaakha.com.np/research/consent-first-sharing-survey Authors: Bibhushan Saakha (Independent researcher; Kathmandu University (B.E. Computer Engineering, 2025)). Working paper, 2026-10-01. Abstract: Background: Menstrual-tracking apps increasingly let users share cycle information with a partner or relative, yet little is known about how young people in South Asia, where menstruation is often restricted at home, regard such sharing. Method: A student team fielded an online, gender-branched questionnaire to young adults in Nepal in April–May 2025 (n = 171; women's branch 106, men's branch 65), recruited through its own networks. I report descriptive statistics, crosstabs with counts and two exploratory tests. Results: Of 105 women who answered, 61.0% tracked with an app, but mostly dates (87.7%) rather than mood (29.2%) or pain (31.1%); 74.5% had been restricted from an activity because of their period. Asked whether a partner or family member could follow their cycle, 49.1% said yes, 32.1% said yes only if they controlled what was seen, and 36.8% also ticked that they preferred to manage it alone. Men rated knowing her symptoms and need for rest highly (86.2% at 4–5 of 5), yet only 10.8% wanted symptom updates, and 52.3% said reminders should depend on her choice. Conclusions: In this convenience sample, sharing was wanted but conditional. The findings support consent-first sharing: off by default, scoped item by item, revocable without notice, and delivering interpretations rather than logs. The sample is young and network-recruited, and the study had no ethics-board review. ## 1. Introduction A period-tracking app that lets a user share her cycle with someone else is, in practice, a consent system. It decides what the other person can see, from when, for how long, and what they learn when access ends. Most of the menstrual-tracking literature in human-computer interaction (HCI) treats tracking as a personal practice, and most privacy work treats it as a relationship between the user and a company. Sharing with a partner, a mother or a sister sits between those two framings, and it has received less attention. The question matters more in some places than in others. In Nepal, menstruation is still widely associated with impurity, and many women and girls are restricted from worship, cooking, touching others or sleeping at home during their period (Amatya et al., 2018; Mukherjee et al., 2020; Thapa & Aro, 2021). Phones in South Asian households are also often shared with or checked by family members (Ahmed et al., 2017; Sambasivan et al., 2018). Under those conditions, the visibility of menstrual data inside the household is a design problem in its own right, not only a question of what a server stores. This paper reports a survey run in April–May 2025 for Myra, a student women's-health venture in Nepal that I founded with three co-founders. Myra proposed a "Companion Mode" through which a partner or family member could follow a woman's cycle. Before building anything, the team asked young women whether they would want this, and asked young men what they would want to receive. The venture has since paused, and no app was released. The survey is the only primary dataset the project produced, and I report it here as a compact, descriptive study with its limits stated. I address three research questions: - **RQ1 (tracking practices).** How do young women in this sample track their cycle, and what do they track? - **RQ2 (perceived impact and stigma).** How do respondents describe the physical, emotional and social impact of menstruation, including restriction at home? - **RQ3 (companion sharing).** Would women let a partner or family member follow their cycle, and on what conditions? What would the men who might receive that information want from it? The contribution is modest: a two-sided, descriptive account from an under-studied setting, and a set of design implications for what I call consent-first sharing. Section 2 places the study in prior work. Section 3 describes the instrument, the sample and the analysis. Section 4 reports results by research question, and Section 5 discusses design implications and what changed in Myra's design. Section 6 sets out the threats to validity, which are substantial. A note on voice: "we" refers to the Myra team, which wrote and fielded the questionnaire; "I" refers to the analysis and this write-up, which are my own. ## 2. Related work ### 2.1 Menstrual tracking in HCI Epstein et al. (2017) combined 2,000 app reviews, a survey of 687 people and follow-up interviews to examine why and how women track their cycles. They found varied reasons, including remembering and predicting a period and informing conversations with healthcare providers; that tracking methods ranged from apps to simply remembering; that apps fail when predictions are inaccurate; and that existing apps generally ignore life stages such as young adulthood, pregnancy and menopause. Fox and Epstein (2020) showed how menstrual apps are inscribed with particular visions of menstruation, assuming for instance that users are heterosexual women with a "normal" cycle who track to gauge fertility. Pichon et al. (2022) compared the literature on menstruators' identities and needs with how menstrual-tracking apps describe themselves, found narrow characterisations and design for limited needs, and argued for treating an irregular cycle as the norm. Feminist HCI work on intimate health, such as Labella (Almeida et al., 2016), has explored designs that support intimate bodily self-knowledge in the face of taboo. This study draws on these accounts in two ways. Its tracking items ask not only whether people track but what they track, so that the gap between recorded data and experienced symptoms can be described. And it treats partners and relatives as stakeholders whose wants can be asked about directly. ### 2.2 Femtech and the privacy of menstrual data A second body of work concerns what happens to menstrual data once it is collected. Shipp and Blasco (2020) analysed the privacy policies and behaviour of 30 Android menstrual apps and found that reproductive data was, in most cases, not covered by the privacy policies at all. Mehrnezhad and Almeida (2021) evaluated the privacy notices and tracking practices of 30 fertility apps and showed that intimate data is collected and shared beyond users' knowledge or consent. Gross et al. (2021) analysed the ethics of monetising menstruation-app data, and Kressbach (2021) placed menstrual tracking within a wider big-data economy. These concerns are not hypothetical. In 2021 the United States Federal Trade Commission finalised an order against Flo Health, the maker of one of the most widely used period apps, over allegations that it had shared users' health data with marketing and analytics firms despite promising privacy (Federal Trade Commission, 2021). After the 2022 United States Supreme Court decision that ended the federal constitutional right to abortion, Mozilla's review of 25 period and pregnancy apps and wearables gave 18 of them a privacy warning label (Mozilla Foundation, 2022). Malki et al. (2024) studied 20 popular female mHealth apps in that post-Roe setting and reported, among other problems, flawed consent and data-deletion mechanisms. Nepal's legal context differs, but the same apps are used there: in this survey, more than half of app users named Flo. ### 2.3 Sharing intimate and health data with others Contextual integrity holds that privacy concerns appropriate flows of information, judged against the norms of a context, rather than secrecy alone (Nissenbaum, 2004). That framing suits companion sharing: the same information (she may need rest this week) can be appropriate to share with a sister and not with an in-law, and appropriate this month but not the next. Work on family informatics shows that health monitoring is often a collaborative household practice (Pina et al., 2017). Research on intimate relationships shows, at the same time, that sharing features can be turned into monitoring. Levy and Schneier (2020) describe "intimate threats" within families, romantic partnerships, friendships and caregiving relationships: the people closest to us know the answers to our secret questions, have access to our devices and can exercise coercive power over us. Freed et al. (2018) document how abusive partners exploit ordinary technologies and features. Any design that lets one person see another's cycle has to be judged against these risks, not only against its benefits. Flo introduced a partner feature in October 2023: the user shares a code, the connected partner sees cycle phase and general information but not every logged detail, and "stop sharing" immediately revokes access (Flo Health, 2023). I came across it only after the survey, during a later review of competing apps. I mention it because the design reasoning in Section 5 reaches a similar shape from different evidence. ### 2.4 Menstrual health and health technology in South Asia and Nepal In Nepal, chhaupadi, the practice of isolating menstruating women and girls, has been studied mostly in the far west, where adolescent girls have described exile from the home and its effects on their lives (Amatya et al., 2018). Restrictions are not confined to rural districts. In a survey of 1,342 adolescent girls and women in three urban districts of the Kathmandu valley, Mukherjee et al. (2020) found that 83.1% did not pray during menstruation and that mothers commonly encouraged a range of restrictions. Thapa and Aro (2021) argue that the taboo is held in place at several levels at once and needs multilevel interventions. HCI research in neighbouring India has studied menstrual health education as a sensitive, stigmatised topic. Tuli et al. (2018) studied Menstrupedia, a website and comic for menstrual health education, through a feminist HCI lens. Tuli et al. (2019) examined the perspectives of young adults, parents, teachers, social workers and health professionals, and found a disconnect between parents' and teachers' expectations about who should introduce the topic. Kumar and Anderson (2015) studied rural Indian women's mobile phone practices around a maternal-health initiative and showed that, within strict social conventions and patriarchal norms, women exercised agency and mobilised help within their communities. Sambasivan et al. (2018) found that women in India, Pakistan and Bangladesh use "performative" practices, such as app locks, deleting content and avoiding technology, to keep some privacy on phones that others borrow and monitor; Ahmed et al. (2017) report related challenges with shared phone use in Bangladesh. I found little published HCI work on menstrual-tracking apps or companion sharing in Nepal specifically. This study does not fill that gap, but it offers a small, descriptive starting point from a young, urban, connected sample. ## 3. Method ### 3.1 Instrument The Myra team wrote the questionnaire and fielded it as an online Tally form. The draft has six sections: demographics; tracking habits and cycle impact; cultural context and emotional well-being; Companion Mode and support; health, medical and premium features; and final thoughts with an early-access opt-in. Question types were single choice, multiple choice ("select all that apply"), 0–5 rating scales, one full ranking of five features, and open text. The live form differed from the draft in four ways that matter for interpretation: 1. **Gender branching.** A gender question routed respondents into a women's branch (tracking, cycle impact, culture and Companion Mode) or a men's branch (eleven questions about following someone's cycle). The one non-binary respondent answered the women's branch. 2. **Price.** The draft's fixed price tiers were replaced by an open question asking what monthly amount, in Nepali rupees (NPR), the respondent would pay "considering similar pricing in other apps and that basic features stay free". 3. **Untitled fields.** Two fields in the men's branch had no title on the form: a 0–5 scale and a multiple-choice list of things the respondent might want to receive. I report the list as published and do not interpret the scale. 4. **A late device question.** A question about phone platform was answered by only 21 people and is not used. The scale labels for "Rate how openly periods are discussed in your family or community" were not preserved in the export. The draft describes the scale as running between "very open" and "very hidden" without saying which end is 5. I infer from the data that 5 means "very open" (Section 4.3) and flag this as an assumption. ### 3.2 Recruitment and fielding The survey was fielded in April–May 2025; the exact open and close dates were not preserved with the anonymised export. Recruitment was by convenience, through the team's own channels: messages to friends and colleagues, email, Instagram, LinkedIn and student organisations. There were no quotas, no screening beyond the gender branch and no incentive. Respondents could optionally say how they found the survey. Of the 80 who did, 27 (33.8%) named a friend or colleague, 14 (17.5%) named a member of the team, 13 (16.2%) said email, 6 (7.5%) Instagram, 1 (1.2%) Instagram and email, 6 (7.5%) LinkedIn, 6 (7.5%) other social or online channels and 3 (3.8%) a student organisation or competition; 4 (5.0%) gave answers that did not describe a channel. These categories are my own grouping of free text. ### 3.3 Consent and ethics According to the team's questionnaire draft, the form opened with a short statement explaining that the team was building a health app for Nepali women, that responses were anonymous, and that they would be used to help shape the product. Submitting the form was the only indication of agreement. There was no separate consent item, no information about withdrawal or data retention, and no contact for questions outside the team. The study was not reviewed by an institutional review board or ethics committee. I return to this in Section 6. Before this analysis, contact details given for the early-access list were removed from the export. Only aggregates are reported here, and no free-text answer is quoted. ### 3.4 Data preparation The export has 171 rows. In one row the answers had shifted by a column relative to their headers, which pushed its gender answer into the wrong field. I realigned it by moving each answer back under its header, which places it in the women's branch; this is the same correction made in the earlier analysis for the Myra case study. One pair of rows is identical in every answered field. It may be a double submission, but that cannot be confirmed, so both rows are kept. The pair falls in the men's branch, and removing one would change no men's-branch percentage by more than 1.5 percentage points. After realignment the women's branch has 106 respondents (105 women and one non-binary respondent) and the men's branch 65. Free-text answers to the price question were coded to a number by a single coder after the study, for this write-up. A range was coded at its midpoint ("300–500" as 400), and "up to" or "under" an amount as that amount; unsure or conditional answers were coded as unsure. Open answers to "What is one thing that is currently missing from existing period tracking methods or apps?" were grouped into themes by that single coder, with no second rater and no codebook prepared in advance. ### 3.5 Analysis The analysis is descriptive. For each question I report the number who answered (n), and counts and percentages of those who answered. For 0–5 scales I report the mean and the share rating 4 or 5. For the feature ranking I report counts at each rank and the mean rank. Multiple-choice percentages do not sum to 100. I report two crosstabs: comfort with Companion Mode against the option "I prefer to manage it alone", because it bears directly on RQ3; and restriction against the openness scale, which also serves as a check on the inferred scale direction. For these I ran two exploratory tests: a two-sided Fisher's exact test on a 2 × 2 table, and a two-sided Mann–Whitney U test with the rank-biserial correlation as effect size. Neither test was specified before data collection. With two tests, a Bonferroni-adjusted threshold would be 0.025. Given the sampling, I treat both as descriptions of patterns within this sample, not as estimates for any population. For the same reason I give 95% Wilson intervals only for the main sharing items, as a rough indication of precision under assumptions that this sample does not meet. The analysis was done in Python with the standard library and SciPy. ## 4. Results ### 4.1 Sample [Figure: Figure 1. Sample profile. Age and language n = 171; channel n = 80 who said how they found the survey. Convenience sample, network-recruited, student-heavy, 18–24 skew; not representative of women in Nepal.] | | Women's branch (n = 106) | Men's branch (n = 65) | All (n = 171) | |---|---|---|---| | Under 18 | 3 (2.8%) | 1 (1.5%) | 4 (2.3%) | | 18–24 | 94 (88.7%) | 55 (84.6%) | 149 (87.1%) | | 25–34 | 9 (8.5%) | 9 (13.8%) | 18 (10.5%) | | Over 34 | 0 | 0 | 0 | | Prefers English | 61 (57.5%) | 34 (52.3%) | 95 (55.6%) | | Prefers both | 40 (37.7%) | 30 (46.2%) | 70 (40.9%) | | Prefers Nepali | 5 (4.7%) | 1 (1.5%) | 6 (3.5%) | *Table 1. Age and preferred language, by branch.* The sample is young and comfortable in English. No respondent was over 34, and only six preferred Nepali alone. Every result below describes this sample. ### 4.2 RQ1: Tracking practices Of the 105 women's-branch respondents who answered the tracking question, 64 (61.0%) tracked with an app, 11 (10.5%) in a notebook or calendar and 6 (5.7%) with mental notes; 17 (16.2%) tried to remember and 7 (6.7%) had never tracked. Among the 64 app users, 34 (53.1%) named Flo and 10 (15.6%) Period Calendar; the rest named a phone's built-in health or calendar app, Clue or MeetYou. [Figure: Figure 2. What women track compared with what they often experience. Women's branch, n = 106. Convenience sample, student-heavy, 18–24 skew; not representative.] | What do you track? (multiple choice, n = 106) | n | % | |---|---|---| | Dates | 93 | 87.7 | | Cramps or pain | 33 | 31.1 | | Flow intensity | 32 | 30.2 | | Mood | 31 | 29.2 | | Skin or hair changes | 8 | 7.5 | | Nothing at all | 6 | 5.7 | *Table 2. Items tracked.* Tracking centred on the date: 51 respondents (48.1%) tracked dates and nothing else. Self-rated understanding of one's own cycle was moderate (n = 106; mean 3.35 on the 0–5 scale; 54, or 50.9%, at 4–5). The gap between what was tracked and what was experienced is clearest for mood. Of the 69 women who rated mood swings at 4–5 in frequency, 26 tracked mood. Of the 53 who rated cramps at 4–5, 22 tracked cramps or pain. ### 4.3 RQ2: Perceived impact and stigma [Figure: Figure 3. Symptom frequency, 0 = never to 5 = very often, sorted by the share at 4–5. Women's branch, n = 106. Same sample limits.] | Symptom (0–5, n = 106) | Mean | Rated 4–5 | |---|---|---| | Mood swings | 3.66 | 69 (65.1%) | | Cramps | 3.28 | 53 (50.0%) | | Bloating | 3.02 | 46 (43.4%) | | Fatigue | 2.77 | 43 (40.6%) | | Anxiety or stress | 3.02 | 42 (39.6%) | | Headaches | 1.41 | 12 (11.3%) | *Table 3. Symptom frequency.* Mood swings were the most frequently reported symptom. The cycle's impact on daily life had a mean of 3.07 (n = 106), with 41 (38.7%) at 4–5. Asked which emotions they associated with their cycle (choose two or three; n = 106), 78 (73.6%) chose irritation and 48 (45.3%) sadness, against 9 (8.5%) relief and 3 (2.8%) empowerment. Asked whether mental health affects their period (n = 106), 37 (34.9%) said "yes, deeply" and 36 (34.0%) "sometimes". Interest in mood and mental well-being features was high: mean 4.00 (n = 106), with 74 (69.8%) at 4–5. Restriction was common. Of 106 respondents, 79 (74.5%) said they had been restricted from doing something because of their period, 23 (21.7%) said no and 2 (1.9%) preferred not to say. Two more did not tick an option but described a restriction around worship (puja) in their own words. Openness of discussion at home or in the community had a mean of 3.45 (n = 106). Respondents who had been restricted rated it lower (n = 79; mean 3.30, median 3) than those who had not (n = 23; mean 4.13, median 5). Within this sample the difference is unlikely to be due to chance alone (Mann–Whitney U = 554, p = .003; rank-biserial correlation 0.39). This is the pattern one would expect if 5 means "very open", and it is the basis for my inferred scale direction. Read the other way round, restricted respondents would be describing their families as more open, which is less plausible. The inference remains an assumption. ### 4.4 RQ3: Willingness and conditions for companion sharing #### Women's branch Women were asked: "Would you feel comfortable if your partner/family member could follow your cycle respectfully through 'Companion Mode'?" | Response (n = 106) | n | % | 95% Wilson interval | |---|---|---|---| | Yes, definitely | 52 | 49.1 | 39.7–58.4 | | Maybe, if I could control what they see | 34 | 32.1 | 24.0–41.5 | | No, not comfortable | 11 | 10.4 | 5.9–17.6 | | Not sure | 9 | 8.5 | 4.5–15.4 | *Table 4. Comfort with a companion following one's cycle.* Taken together, 86 of 106 (81.1%) were open to some form of sharing, but for a large minority that openness was explicitly conditional on control. The question named the feature and called it "respectful", which may have raised acceptance (Section 6). [Figure: Figure 4. The two-sided result. Women n = 106, men n = 65. Convenience sample, network-recruited, student-heavy, 18–24 skew; not representative. The men's 'what would you want to receive' item was an untitled field on the form; options are shown as published.] Asked whom they would trust as a companion (multiple choice, n = 106), 62 (58.5%) chose a romantic partner, 26 (24.5%) their mother, 25 (23.6%) a close friend and 24 (22.6%) a sister. Thirty-nine (36.8%) ticked "I prefer to manage it alone"; of those 39, 24 ticked no one else and 15 also ticked at least one person. | Comfort response | n | Also ticked "manage it alone" | % | |---|---|---|---| | Yes, definitely | 52 | 10 | 19.2 | | Maybe, if I could control what they see | 34 | 16 | 47.1 | | No, not comfortable | 11 | 9 | 81.8 | | Not sure | 9 | 4 | 44.4 | | All | 106 | 39 | 36.8 | *Table 5. Comfort with Companion Mode against the wish to manage alone.* The wish to manage alone rose as comfort fell, as one would expect. Two features of the table are more informative. One in five women who said "yes, definitely" also said they would prefer to manage alone, so wanting support and wanting privacy were not opposites. And nearly half of the conditional group ticked "manage it alone". Comparing only the "yes" and conditional groups, the difference is unlikely to be due to chance alone within this sample (Fisher's exact test, two-sided, p = .008; odds ratio 0.27). I did not run a chi-squared test on the full 4 × 2 table because three cells have expected counts below five. Restriction did not obviously change willingness. Among the 79 restricted respondents, 36 (45.6%) said "yes, definitely" and 27 (34.2%) gave the conditional answer; among the 23 who had not been restricted, the figures were 15 (65.2%) and 5 (21.7%). These subgroups are small, and I did not test the difference. Asked what support from a companion would help most (multiple choice, n = 106), 78 (73.6%) chose emotional support and understanding, 76 (71.7%) understanding when they might be experiencing discomfort or mood changes, 63 (59.4%) reminders for self-care such as rest and hydration, and 51 (48.1%) practical help with tasks. #### Men's branch Of the 65 men, 16 (24.6%) had ever used a period-tracking app for someone else. Asked whom they would use Companion Mode for (multiple choice, n = 65), 43 (66.2%) chose a girlfriend, 38 (58.5%) their mother, 35 (53.8%) a sister, 29 (44.6%) a friend and 17 (26.2%) a wife. The low share for "wife" fits the age profile. [Figure: Figure 5. Whom women would trust and whom men would follow, and men's attitudes to consent and prompts. Women n = 106, men n = 65. Same sample limits.] | How important is it to know… (0–5, n = 65) | Mean | Rated 4–5 | |---|---|---| | Her physical symptoms (e.g., cramps, fatigue) | 4.42 | 56 (86.2%) | | When she might need rest or comfort | 4.42 | 56 (86.2%) | | The start and end of her period | 4.12 | 51 (78.5%) | | Her mood changes | 3.95 | 45 (69.2%) | | Her ovulation or fertility phase | 3.88 | 44 (67.7%) | *Table 6. What men rated as important to know.* | What would you want to receive? (multiple choice, untitled field, n = 65) | n | % | |---|---|---| | Daily mood or health summary | 41 | 63.1 | | Notifications about expected period days | 35 | 53.8 | | Tips for emotional support | 33 | 50.8 | | Meal or exercise suggestions | 27 | 41.5 | | Educational resources | 15 | 23.1 | | Gift or care-package reminders | 10 | 15.4 | | Symptom updates | 7 | 10.8 | | Chat prompts or check-in ideas | 2 | 3.1 | *Table 7. What men would want to receive.* The contrast between Tables 6 and 7 is the clearest two-sided finding. Men rated knowing her symptoms as very important, but few chose symptom updates as something to receive; they chose summaries and guidance instead. Asked whether the app should suggest supportive actions, such as sending a kind message or giving space, 44 (67.7%) said yes, 18 (27.7%) maybe and 3 (4.6%) no. Asked whether they would want reminders or tips about being emotionally available during her period, 34 (52.3%; 95% Wilson interval 40.4–64.0) chose "only if she chooses to share that info", 29 (44.6%) yes and 2 (3.1%) no. The conditional answer was offered by the form rather than volunteered, but a majority chose it over an unconditional yes. ### 4.5 Help channels, feature priorities and price These items were not part of the research questions, but they give context for the design discussion. [Figure: Figure 6. Feature ranking (n = 106) and preferred help channel if one's pattern changes suddenly (n = 105). Women's branch. Same sample limits.] If their cycle pattern changed suddenly (multiple choice, n = 105), 61 (58.1%) would want to talk to a doctor in the app, 29 (27.6%) would want automated suggestions, 20 (19.0%) would answer quick surveys, and 25 (23.8%) chose none of these. | Feature (n = 106; 1 = most important) | Mean rank | Ranked first | Ranked last | |---|---|---|---| | Nutrition and fitness plans for periods | 2.25 | 36 | 4 | | Doctor consultation via app | 2.85 | 20 | 12 | | Pregnancy tracking and reminders | 3.04 | 26 | 28 | | Reminders for period-product refills | 3.19 | 20 | 28 | | Community support space for women | 3.68 | 4 | 34 | *Table 8. Ranking of five features.* Pregnancy tracking was polarised: 26 respondents ranked it first and 28 last. Asked which premium features they would pay for (multiple choice, n = 106), 71 (67.0%) ticked "I would prefer a completely free app", and 55 (51.9%) ticked only that option; 29 (27.4%) would pay for telehealth consultations. In the open price question (n = 106), 70 respondents named a positive monthly amount, 20 gave zero, 14 were unsure and 2 preferred to pay per consultation. Among the 70, the median was NPR 225 and the mean NPR 388, pulled up by four answers of NPR 2,000. These figures depend on my coding of free text. ### 4.6 Open answers Ninety-five respondents answered the open question about what is missing from current tools (62 in the women's branch, 33 in the men's). Many answers were non-substantive ("don't know", "never used one"). Among the rest, the most frequent themes in my single-coder grouping were access to a doctor, nutrition and diet, local context (such as Nepali calendar dates and festivals), the companion feature itself, and emotional well-being. Three answers raised consent or anonymity directly, including a request that both people agree to what is shared. Two described notifications from an existing app that felt embarrassing or alarming, such as a "late" warning after a single day. These counts are small and soft, and I use them only to illustrate the closed-question results. ## 5. Discussion ### 5.1 Summary For RQ1, tracking in this sample was common but shallow: most women recorded dates, and few recorded the mood and pain they reported experiencing often. For RQ2, menstruation was described in emotional terms, mostly irritation and sadness; three in four women had experienced restriction, which went with lower ratings of openness at home. For RQ3, about half the women would let a companion follow their cycle without conditions and a third only with control over what is seen, while a third would rather manage alone, including some who also said yes. The men wanted to understand and help, but asked for interpretations and suggestions rather than a feed, and half made emotional prompts conditional on her choice. ### 5.2 Sharing is conditional, not binary The most important result for design is that willingness and the wish for privacy co-existed. A single "share with partner" switch would treat the 52 women who said yes and the 34 who said yes-with-control as one group, and would offer nothing to the 10 who said yes and also preferred to manage alone. In contextual-integrity terms (Nissenbaum, 2004), these respondents were not refusing a flow of information; they were asking to set its norms. A companion feature should let them do that item by item and person by person. The finding about relationships points the same way. Women most often named a romantic partner, but men would follow a mother or sister almost as often as a girlfriend. A sharing model built for one romantic partner fits neither side's answers. In a joint-family household, different relatives would plausibly warrant different grants. ### 5.3 Receivers want interpretation The men's answers suggest that the useful unit of sharing is an interpretation, not a record. "She may need more rest this week", with one suggested action, matches what they asked for (a summary and tips) and avoids what women were wary of (someone else reading their logs). It also reduces what can be misused. Research on intimate relationships shows how ordinary sharing features become tools for monitoring and control (Freed et al., 2018; Levy & Schneier, 2020). An interpretation layer does not remove that risk, but it limits the detail available to a controlling partner, and it is compatible with stopping silently. ### 5.4 Household visibility is part of privacy Most femtech privacy research concerns flows to companies, advertisers and, since 2022, law enforcement (Shipp & Blasco, 2020; Mozilla Foundation, 2022; Malki et al., 2024). In this sample, three quarters of women had been restricted at home, and prior work in the region shows that phones are borrowed and checked by family members (Ahmed et al., 2017; Sambasivan et al., 2018). For these users, a lock-screen notification that says "your period is due" can be a disclosure to a parent. Privacy design for this context has to consider who might see the phone, not only who receives the data. ### 5.5 Design implications: consent-first sharing I summarise the implications as five properties of consent-first sharing. They are design hypotheses drawn from survey answers, not tested results. 1. **Off by default.** Nothing is shared until she chooses an item. Inviting a companion shares nothing by itself. 2. **Scoped.** Grants are per item (for example, a derived rest signal, period dates, a weekly mood summary) and per person. More sensitive items, such as raw symptoms or the fertility window, need an extra confirmation. Private notes cannot be shared. 3. **Interpreted.** The companion sees derived signals and suggested actions, never her logs. 4. **Revocable without notice.** She can remove one item or stop sharing entirely at any time. Anything that reduces access is silent: the companion sees less, or "sharing paused", without being told what was removed or why. 5. **Discreet.** Notification text is generic by default, and an anonymous or private mode blocks sharing rather than running alongside it. ### 5.6 What changed in Myra [Figure: Figure 7. Companion Mode for the cycle, both sides, as redrawn wireframes: per-person grants that start off, a short invite code that shares nothing yet, and a derived signal with one suggested action on the companion's phone. Concept, not built or tested; fictional data.] The survey changed Myra's design in specific ways. Before it, Companion Mode was one of five broad pillars in the pitch, described loosely as letting partners and family follow a cycle. Afterwards it became the centre of the concept, specified as the interpretation layer above, with per-person grants that start off, a silent stop, and a rule that anonymous mode and sharing cannot run together. Mood moved into the same quick log as flow and pain. Notification copy became discreet by default. Language became an explicit choice at the start of onboarding, because the sample did not support a Nepali-only default but also could not speak for Nepali-only users. Clinician access was framed as a service around a free core, because many respondents wanted a doctor but few would pay a subscription. The survey also argued against a later direction. In 2026 the team explored a pregnancy-first product. This sample, almost entirely aged 18–24 and split on pregnancy tracking, cannot support that direction, and the lack of a sample of pregnant and postpartum women is part of why it stalled. Myra is now paused. None of the design above was built or tested with users, and there is no measured outcome. ## 6. Limitations and threats to validity **Convenience sample.** Respondents were recruited through the team's own networks, and half of those who said how they found the survey named a friend, colleague or team member. Selection probably favours people who are comfortable discussing menstruation, open to new apps and well disposed towards the team. The results describe people like the team's contacts, not women or men in Nepal. **Age and language skew.** Of 171 respondents, 149 (87.1%) were 18–24 and none was over 34; only six preferred Nepali alone. The sample says nothing about older women, married women in joint families, rural women or people who do not read English, who are exactly the groups for whom household visibility may matter most. **Self-report and stated intention.** A survey measures what people say. Answering "maybe, if I could control what they see" is not the same as linking a sister's phone, and stated willingness to pay is a weak guide to paying. Social desirability may have raised men's ratings of how much they want to understand. **Framing and instrument.** The team that wanted to build Companion Mode wrote the questions. The comfort item named the feature and called it "respectful", which may have raised acceptance. The men's branch offered a consent-conditional option, so the 52.3% who chose it chose from a list rather than volunteering the condition. Two men's fields were untitled; the openness scale had no exported labels, so its direction is inferred; one option on the mental-health item appears in the export as "Not rarely", where the draft has "Not really" (5 respondents); and the device question was added late. As far as I have a record, the form was not piloted. Fielding dates and the exact live wording of the introduction were not kept with the export. **Analysis after the fact.** The analysis was done after the study, for this write-up, by a single analyst. Free-text price amounts and open answers were coded by one coder, with no second rater and no prior codebook, so those numbers are soft. The two statistical tests were exploratory, and with a non-probability sample their p-values describe patterns within these data, not population effects. **Interviews.** The team also held informal interviews and conversations alongside the survey. No notes were kept, so nothing in this paper relies on them. **No ethics review.** The study was not reviewed by an ethics board. Consent was implied by submitting a form that described its purpose and promised anonymity; there was no explicit consent item and no information about withdrawal or retention. The form also offered an early-access list, which sits uneasily beside the promise of anonymity. The topic is sensitive, and four respondents reported being under 18. A future study should obtain ethics approval, use an information sheet and an explicit consent step, handle minors appropriately and separate contact details from responses at the point of collection. **Researcher position.** I was the founder analysing demand for my own product. I have tried to counter that by reporting the counts that cut against the venture (a third of women would rather manage alone; 28 women ranked pregnancy tracking last; most would not pay) beside the ones that supported it. ## 7. Conclusion In a young, network-recruited sample in Nepal, most women who answered tracked only the date, although mood swings and cramps were common, and three in four had been restricted because of their period. Most were open to a partner or relative following their cycle, but a third of all women only on the condition of control, and a third would rather manage alone. The men who might receive that information wanted interpretations and suggestions rather than symptom feeds, and half wanted emotional prompts only if she chose to share. These are descriptive findings from a convenience sample, collected without ethics review, and they cannot be generalised. They do point in a consistent direction for design: sharing in menstrual and reproductive health tools should be consent-first, meaning off by default, scoped by item and person, interpreted rather than raw, revocable without notice, and discreet on a phone that others may see. Testing that direction properly would need an ethics-reviewed interview study with a wider range of women, including Nepali-only speakers and women outside Kathmandu, and a usability study of the consent flow in which the main measure is whether a participant can say correctly what a companion will see. ## Acknowledgements I thank my Myra co-founders, all students at the time: the co-founder responsible for healthcare strategy and marketing (and later research synthesis), the technical lead, and the co-founder responsible for data and analytics. The team wrote and distributed the questionnaire together. I also thank everyone who answered it and the friends who passed it on. The analysis, interpretation and any errors here are mine. ## References Ahmed, S. I., Haque, M. R., Chen, J., & Dell, N. (2017). Digital privacy challenges with shared mobile phone use in Bangladesh. *Proceedings of the ACM on Human-Computer Interaction, 1*(CSCW), 1–20. https://doi.org/10.1145/3134652 Almeida, T., Comber, R., Wood, G., Saraf, D., & Balaam, M. (2016). On looking at the vagina through Labella. In *Proceedings of the 2016 CHI Conference on Human Factors in Computing Systems* (pp. 1810–1821). ACM. https://doi.org/10.1145/2858036.2858119 Amatya, P., Ghimire, S., Callahan, K. E., Baral, B. K., & Poudel, K. C. (2018). Practice and lived experience of menstrual exiles (Chhaupadi) among adolescent girls in far-western Nepal. *PLOS ONE, 13*(12). https://doi.org/10.1371/journal.pone.0208260 Epstein, D. A., Lee, N. B., Kang, J. H., Agapie, E., Schroeder, J., Pina, L. R., Fogarty, J., Kientz, J. A., & Munson, S. A. (2017). Examining menstrual tracking to inform the design of personal informatics tools. In *Proceedings of the 2017 CHI Conference on Human Factors in Computing Systems* (pp. 6876–6888). ACM. https://doi.org/10.1145/3025453.3025635 Federal Trade Commission. (2021, June 22). *FTC finalizes order with Flo Health, a fertility-tracking app that shared sensitive health data with Facebook, Google, and others* [Press release]. https://www.ftc.gov/news-events/news/press-releases/2021/06/ftc-finalizes-order-flo-health-fertility-tracking-app-shared-sensitive-health-data-facebook-google Flo Health. (2023, October 19). *Flo for Partners now available: Unlocking better sex, conception & connection among couples* [Press release]. PR Newswire. https://www.prnewswire.com/news-releases/flo-for-partners-now-available-unlocking-better-sex-conception--connection-among-couples-301961472.html Fox, S., & Epstein, D. A. (2020). Monitoring menses: Design-based investigations of menstrual tracking applications. In C. Bobel, I. T. Winkler, B. Fahs, K. A. Hasson, E. A. Kissling, & T.-A. Roberts (Eds.), *The Palgrave handbook of critical menstruation studies* (pp. 733–750). Palgrave Macmillan. https://doi.org/10.1007/978-981-15-0614-7_54 Freed, D., Palmer, J., Minchala, D., Levy, K., Ristenpart, T., & Dell, N. (2018). "A stalker's paradise": How intimate partner abusers exploit technology. In *Proceedings of the 2018 CHI Conference on Human Factors in Computing Systems* (pp. 1–13). ACM. https://doi.org/10.1145/3173574.3174241 Gross, M. S., Hood, A., & Corbin, B. (2021). Pay no attention to that man behind the curtain: An ethical analysis of the monetization of menstruation app data. *International Journal of Feminist Approaches to Bioethics, 14*(2), 144–156. https://doi.org/10.3138/ijfab-2021-03-22 Kressbach, M. (2021). Period hacks: Menstruating in the big data paradigm. *Television & New Media, 22*(3), 241–261. https://doi.org/10.1177/1527476419886389 Kumar, N., & Anderson, R. J. (2015). Mobile phones for maternal health in rural India. In *Proceedings of the 33rd Annual ACM Conference on Human Factors in Computing Systems* (pp. 427–436). ACM. https://doi.org/10.1145/2702123.2702258 Levy, K., & Schneier, B. (2020). Privacy threats in intimate relationships. *Journal of Cybersecurity, 6*(1), tyaa006. https://doi.org/10.1093/cybsec/tyaa006 Malki, L. M., Kaleva, I., Patel, D., Warner, M., & Abu-Salma, R. (2024). Exploring privacy practices of female mHealth apps in a post-Roe world. In *Proceedings of the CHI Conference on Human Factors in Computing Systems* (pp. 1–24). ACM. https://doi.org/10.1145/3613904.3642521 Mehrnezhad, M., & Almeida, T. (2021). Caring for intimate data in fertility technologies. In *Proceedings of the 2021 CHI Conference on Human Factors in Computing Systems* (pp. 1–11). ACM. https://doi.org/10.1145/3411764.3445132 Mozilla Foundation. (2022). *In post Roe v. Wade era, Mozilla labels 18 of 25 popular period and pregnancy tracking tech with \*Privacy Not Included warning*. https://www.mozillafoundation.org/en/blog/in-post-roe-v-wade-era-mozilla-labels-18-of-25-popular-period-and-pregnancy-tracking-tech-with-privacy-not-included-warning/ Mukherjee, A., Lama, M., Khakurel, U., Jha, A. N., Ajose, F., Acharya, S., Tymes-Wilbekin, K., Sommer, M., Jolly, P. E., Lhaki, P., & Shrestha, S. (2020). Perception and practices of menstruation restrictions among urban adolescent girls and women in Nepal: A cross-sectional survey. *Reproductive Health, 17*, 81. https://doi.org/10.1186/s12978-020-00935-6 Nissenbaum, H. (2004). Privacy as contextual integrity. *Washington Law Review, 79*(1), 119–157. https://digitalcommons.law.uw.edu/wlr/vol79/iss1/10/ Pichon, A., Jackman, K. B., Winkler, I. T., Bobel, C., & Elhadad, N. (2022). The messiness of the menstruator: Assessing personas and functionalities of menstrual tracking apps. *Journal of the American Medical Informatics Association, 29*(2), 385–399. https://doi.org/10.1093/jamia/ocab212 Pina, L. R., Sien, S.-W., Ward, T., Yip, J. C., Munson, S. A., Fogarty, J., & Kientz, J. A. (2017). From personal informatics to family informatics: Understanding family practices around health monitoring. In *Proceedings of the 2017 ACM Conference on Computer Supported Cooperative Work and Social Computing* (pp. 2300–2315). ACM. https://doi.org/10.1145/2998181.2998362 Sambasivan, N., Checkley, G., Batool, A., Ahmed, N., Nemer, D., Gaytán-Lugo, L. S., Matthews, T., Consolvo, S., & Churchill, E. (2018). "Privacy is not for me, it's for those rich women": Performative privacy practices on mobile phones by women in South Asia. In *Proceedings of the Fourteenth Symposium on Usable Privacy and Security (SOUPS 2018)*. USENIX Association. https://www.usenix.org/conference/soups2018/presentation/sambasivan Shipp, L., & Blasco, J. (2020). How private is your period? A systematic analysis of menstrual app privacy policies. *Proceedings on Privacy Enhancing Technologies, 2020*(4), 491–510. https://doi.org/10.2478/popets-2020-0083 Thapa, S., & Aro, A. R. (2021). "Menstruation means impurity": Multilevel interventions are needed to break the menstrual taboo in Nepal. *BMC Women's Health, 21*, 84. https://doi.org/10.1186/s12905-021-01231-6 Tuli, A., Chopra, S., Kumar, N., & Singh, P. (2018). Learning from and with Menstrupedia: Towards menstrual health education in India. *Proceedings of the ACM on Human-Computer Interaction, 2*(CSCW), 1–20. https://doi.org/10.1145/3274443 Tuli, A., Dalvi, S., Kumar, N., & Singh, P. (2019). "It's a girl thing": Examining challenges and opportunities around menstrual health education in India. *ACM Transactions on Computer-Human Interaction, 26*(5), 1–24. https://doi.org/10.1145/3325282 # Paper: Count first, compare second: a design case of cash-session reconciliation in point-of-sale software for small businesses in Nepal URL: https://www.bibhushansaakha.com.np/research/cash-reconciliation-design-case Authors: Bibhushan Saakha (BIC Technology (Tigg), Kathmandu, Nepal). Design case study, 2026-10-02. Abstract: Small shops and restaurants in Nepal often cannot say whether the cash in a drawer is right, how much is missing, or why. This design case documents how a point-of-sale product used in Nepal moved from sessions that recorded only a time and a person to an accountable cash drawer. The author, the product's sole designer and its web frontend engineer, describes the decisions and their rationale: modelling the drawer as a ledger whose expected amount can be re-added by eye; a blind, two-phase close in which the cashier counts before the expected amount is shown, to reduce anchoring toward it; treating a blank count as unknown and zero as a known empty drawer; requiring a note only when the drawer is not balanced; a per-location choice between strict and easy modes; a single gate at the payment page; and a per-entry history of corrections. The paper traces iterations, most of which removed something, and derives five transferable principles. It compares the design with patterns documented by other POS products in a 2026 documentation scan. It is not an evaluation: there was no field study and no outcome has been measured. The paper closes with a proposed study combining shift-change observation, discrepancy logging with explicit denominators, and a controlled test of the blind count. ## 1 Introduction A cashier in a small shop or restaurant opens the cash drawer in the morning, takes orders all day, pays a supplier out of the till, accepts cash from a regular customer who is settling last week's credit, and hands the counter over in the evening. At the end of the day the owner has a simple question: is the right amount of money in the drawer? If not, how much is missing, since when, and why? Point-of-sale (POS) software is usually good at the first half of that day, recording what was sold, and often weaker at the second half, accounting for the physical cash that the sales should have produced. This paper is about a product where, before the work described here, that second half was missing entirely. Tigg is a cloud accounting and POS product made by BIC Technology in Kathmandu, used by retail shops and restaurants in Nepal. Its POS already had *sessions*: a person started a session at a location, sold things, and stopped it. A session recorded a time and a person. It did not record money. There was no opening count, no record of cash taken out or put in without a sale, no closing count, and nothing to compare a count against. When a drawer came up short, the difference had no owner, no timestamp and no explanation. Between June and July 2026 I designed, and built the web frontend for, a cash layer on top of those sessions. Each session can now carry an opening count, cash movements with reasons, a closing count that is compared with an expected amount, a required explanation when the two disagree, and a visible history of corrections. All of this sits behind a per-location setting, so that shops which do not want the control are not slowed down by it. The paper's contribution is a documented set of design decisions and the reasoning behind each, written in the *design case* genre: a rich, situated account of a designed artefact, intended to be useful to other designers as precedent (Boling, 2010; Smith, 2010). From those decisions I draw five design principles that I believe transfer to other systems where people count something and a computer holds the expected answer. The paper is explicitly **not an evaluation**. I did not run a field study, a usability test or an experiment, and no outcome has been measured. Section 7 says what that means for the claims made here and sets out a study that could test them. ## 2 Background and related work ### 2.1 Cash handling and reconciliation practice Reconciling a cash drawer is a practice that predates software: count the float at the start of a shift, record every movement of cash, count again at the end, and compare the count with what the records say should be there. In internal-control terms it is a *control activity*, a check that detects errors and irregularities after the fact, which depends on the records being complete and on the people involved being accountable for their part (COSO, 2013). Two features of that framing matter for design. First, reconciliation is only as good as its inputs: if cash can leave the drawer without a record, the expected figure is wrong and the comparison is meaningless. Second, a control has to be proportionate to the organisation. A single owner-operator who is the only person touching the till has little use for a ceremony designed to separate the duties of several employees. In HCI, Perry and Ferreira's (2017) study of *moneywork* describes the practical, social and material work people do around digital and physical money. It is a reminder that a cash drawer is not only a number in a database but a physical object handled at a busy counter by a person who may be interrupted, tired or under suspicion. ### 2.2 Anchoring and confirmation The central decision in this design, hiding the expected amount until the cashier has counted, rests on a well-established finding. Tversky and Kahneman (1974) showed that numerical estimates are pulled toward an initial value, even an arbitrary one, and that adjustment away from it is typically insufficient. A later review found the effect robust across many domains, including expert judgement (Furnham & Boo, 2011). In an auditing setting close to cash reconciliation, Joyce and Biddle (1981) studied anchoring and adjustment in auditors' probabilistic judgements, an early sign that professionals who check figures are not immune to the reference values put in front of them. Counting a drawer is not an estimate; there is a true answer. But when a count disagrees with a number on screen, the easiest resolution is to recount until the numbers agree, or to type the target. Nickerson (1998) describes this broader tendency to seek or interpret evidence in ways that confirm an existing expectation. A reconciliation interface that shows the expected figure during counting therefore risks producing counts that are partly copies of the expectation, which defeats the purpose of counting. ### 2.3 Numeric input and form design Much of the integrity of a cash feature lives in small input rules. Thimbleby and Cairns (2010) document how number-entry interfaces commonly ignore, truncate or silently transform erroneous input, and argue that such behaviour causes serious errors in safety-critical settings. Their analysis applies directly to the question of what a blank field should mean. In data modelling the distinction between a known value and a missing one is long established: Codd's (1979) extension of the relational model treats a null as "value unknown", distinct from every actual value, including zero. Nielsen's (1994) heuristic of error prevention frames the choice between blocking, warning and silently accepting. ### 2.4 POS, small businesses and emerging markets I found little published HCI work on POS reconciliation in small businesses. The closest work concerns micro and small enterprises in developing economies, and HCI for development. Donner and Escobari (2010) review evidence on mobile phone use by micro and small enterprises and find that benefits accrue mostly through communication and coordination rather than through changes to internal business processes. Toyama (2010) and Dell and Kumar (2016) survey HCI for development and call for designs grounded in the specific conditions of the settings they serve, and for honesty about how much designers know about the people they design for. The Global Findex database documents the spread of digital payments alongside cash in many economies (Demirgüç-Kunt et al., 2022). In Nepal that coexistence is visible at the counter, where QR and wallet payments sit alongside cash, so a reconciliation design has to separate the cash physically in the drawer from everything else. ### 2.5 Design cases as a research genre This paper follows the design-case genre as developed in design and learning-design research. Boling (2010) argues that designers learn from rich accounts of specific designs, as precedent, and that such accounts are a legitimate form of design knowledge without generalisable claims. Smith (2010) sets out what makes a design case rigorous: a clear account of the designer's situation and constraints, transparency about sources, attention to decisions and to the alternatives set aside, and honesty about failures. In HCI, design knowledge has been framed as intermediate-level concepts, more abstract than an instance but less than a theory (Höök & Löwgren, 2012), and research through design as producing accounts whose value lies in what they make thinkable rather than in proof (Zimmerman et al., 2007; Gaver, 2012). Schön's (1983) reflective practice, reasoning through a conversation with the situation, describes how most decisions in this case were made. The principles in Section 6 are offered in that spirit: as claims to be tested. ## 3 Context and method ### 3.1 Setting Tigg serves small and medium businesses in Nepal: shops, restaurants and similar counters. Its POS runs as a web application on counter desktops and tablets, and as a mobile app. Amounts are in Nepalese rupees. The default denominations a cashier counts are Rs 1,000, 500, 100, 50, 20, 10, 5, 2 and 1, in descending order, which is the order in which a drawer is usually counted. Less common notes such as Rs 25 and Rs 250 are not in the default list, but a shop can add them, which is why the list is editable rather than fixed. Customers commonly run a tab and pay it later, and cash, QR codes and digital wallets are used side by side. All amounts in this paper and its figures are fictional. ### 3.2 My role The request for cash sessions came from sales and went to the CEO, who planned the feature and asked me to ideate and design it. I am the only UI/UX designer at Tigg, and on this feature I was also the web frontend engineer. Table 1 summarises the division of labour. | Area | Who | |---|---| | Plan and priority | CEO | | Interaction and visual design, web and mobile | Author | | Web frontend (the POS web application) | Author | | Backend: storage, the expected-cash calculation, history | Backend engineer | | Mobile app build | Mobile developer, from the author's designs | | Review of the author's frontend work | Senior frontend engineer | | Testing | QA | *Table 1. Roles on the feature. Teammates are identified by role.* Being both designer and frontend engineer meant I could try a decision in the running product within hours. My first working version ran on 2 June 2026, was connected to the real backend on 8 June, and reached production in the second half of June after QA and user acceptance testing. I continued refining it into mid-July. ### 3.3 Sources of evidence The account rests on four kinds of evidence: 1. **Product briefs and conversations** with the CEO during ideation. There was no written requirements document. 2. **Design files**: the web and mobile screens for the feature, marked ready for development. 3. **The shipped behaviour** of the web product, which I built. Interface messages quoted in this paper are the product's own copy. 4. **My own recollection**, recorded as written answers in September 2026, about why particular decisions were made. Where I give a reason that I did not state at the time, I say that it is a retrospective reading. There was one walkthrough with real equipment: the team ran the flow in the office with actual cash drawers and counter hardware. It was a hardware demonstration, not user research; nobody outside the team took part. I also hear about user problems indirectly, through QA, support and the CEO, and QA's follow-up list during the build was the most concrete feedback I had. Before designing, I looked at the cash-session features of a number of other POS products, most often Zoho's, but I did not record which ones. The competitor comparison in Section 6 is therefore based on a separate documentation scan done in September 2026, while writing this paper, and is labelled as such. ### 3.4 Method and its limits This is a retrospective, reflective account by a single practitioner (Schön, 1983), structured to meet Smith's (2010) criteria for a rigorous design case: situation and constraints, decisions and alternatives, iterations and failures, and transparent sources. It can describe what was designed and why, but not whether the design works for the people who use it. Section 7 returns to these limits. ## 4 The design ### 4.1 Objects and states Before drawing screens I separated three properties of a session that are easy to merge: 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. **Viewer**: the session's owner, an admin, or another member of staff. It is tempting to let "closed" imply "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. Keeping the three dimensions independent made that question a single filter in the session list, where Balanced, Short and Excess are nested under Closed and appear only when the location verifies cash. [Figure: Figure 1. Three dimensions of a session that vary independently: lifecycle, cash result and viewer. Keeping them apart is what lets an owner ask for every short session last week in one step.] With the dimensions separated, the states followed (Figure 2). [Figure: Figure 2. The 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 explicit. The dashed path is easy mode, or recovery when a session exists without an opening count.] ### 4.2 The drawer as a ledger The expected amount is not a number the system should simply assert. It is a sum that 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 Writing the drawer as a ledger exposed three gaps that a sales report hides. *Cash that moves without a sale*, such as a change top-up, a supplier paid from the till or an owner taking money out, had no record, so cash in and cash out became entries of their own, each with a reason. *Credit settled in cash* is real cash but not a new sale, so it needed its own row; the backend engineer made sure credit collections reached the session report. And *cash versus everything else*: the drawer should only count what is physically in it, so the drawer view uses the cash share of sales, refunds and collections, and other payment methods stay in a separate transaction summary. [Figure: Figure 3. One 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 almost invisible in a chart, so the interface names it in words: short by Rs 300.] The ledger became the layout. The session detail has a single Cash Drawer card that lists opening, cash sales, refunds, credit collected, cash in, cash out, expected, closing and difference, in that order (Figure 4). The expected amount is computed by the backend, not in the browser. Doing the sum in two places would eventually produce two answers, and in a cash feature two answers are worse than none. The interface's job is to show the parts of the sum so that the total can be checked by eye. [Figure: Figure 4. The session detail before and after. The earlier page showed an overview, sales and notes. The new 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.] ### 4.3 Blind count: count first, compare second The most important decision in the closing flow was deliberate from the start: **the cashier counts before seeing the expected amount**. The reason was to reduce bias toward matching the expected figure. If the expected amount sits next to the input, the count is no longer independent. A cashier who counts Rs 17,280 and sees Rs 17,600 has every reason to recount until the numbers agree, or to type the target (Section 2.2). Closing therefore has two phases (Figure 5). In the *count* phase, the cashier enters a total or counts by denomination, and nothing on screen says what the answer should be. There is no note field yet either, because a note before the comparison would only invite a guess. Only after the cashier presses **Submit Count** does the system fetch the expected cash and show the *review*: expected, counted, the difference, and a plain-language status such as "Short by Rs 300". The status is carried by words as well as colour. [Figure: Figure 5. The 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.] If the expected amount fails to load, the cashier stays on the count, sees "Couldn't fetch the expected cash. Please try again." and can resubmit; the count is not lost. The blind count has one honest boundary. While a session is open, the Cash Drawer card shows the running expected cash, because staff and owners want to see the drawer build up during the day. A cashier who wants the target can find it. The two-phase close removes the target from the *moment of counting*; it does not make the whole product blind. Section 6.2 compares this with products that hide the expected amount by permission. ### 4.4 Empty is not zero A blank opening count and an opening count of zero look alike on screen but 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 be indistinguishable from an honest empty drawer, and every difference computed after it would be wrong. This is Codd's (1979) distinction between a null and a value, applied at the input field, and a refusal of the silent transformation of input that Thimbleby and Cairns (2010) warn against. The rules differ by kind of entry (Table 2). A *balance* is a state, and zero is a legitimate state, so a typed 0 is accepted and stays visible in the field. A *movement* is an event, and a movement of zero did not happen, so cash in and cash out need an amount above zero. | Input | Outcome | Message | |---|---|---| | Opening count left blank | Blocked; nothing saved | "Enter an opening amount." | | Opening count of 0 | Accepted; the 0 stays visible | None | | Closing count left blank | Blocked; expected amount not requested | "Enter a counted amount." | | Cash in or out of 0, or blank | Blocked | "Enter an amount greater than zero." | | Cash in or out without a note | Blocked; the form scrolls to the note | "Note is required." | | Denomination count with every row at 0 | Valid as a balance, not as a movement | As above | *Table 2. Input rules for balances and movements, with the interface's own messages.* [Figure: Figure 6. Four 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.] ### 4.5 A required note when the drawer is not balanced When the drawer is balanced, the closing note is optional. When it is short or in excess, the note becomes required: its label changes from optional to required, and pressing Close Session with the note empty shows "A note is required when the drawer is not balanced." and scrolls to the field. A mismatch means something has gone wrong, and the record should say what. The same rule 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 did not make the note required on every close. A note that is always mandatory tends to become a single word typed forty times a week, and then it carries no information. Asking only when the numbers disagree keeps the request rare, which I expect makes a real answer more likely. That expectation is untested (Section 7). ### 4.6 Strict and easy modes Some shops want strict control over cash. Others, such as an owner who is the only person at the till, would experience forced counts as friction. The design answers this with a per-location setting labelled **Require Cash Verification**, with the helper text "Require users to record opening and closing counted cash for each session." The label was chosen by the CEO. Internally we called the two states strict and easy mode (Table 3). | | Strict (verification on) | Easy (verification off) | |---|---|---| | Start a session | Opening-count modal | One click | | Take payment without an opening count | Asked for the count at payment | Allowed | | Close a session | Two-phase blind count and review | One click | | Record counts | Required | Optional; can be added later from the drawer card | | Cash result in the session list | Balanced, Short, Excess | Not shown | *Table 3. What the location setting changes.* The setting lives on the location, on the server, so every device at a counter behaves the same way; the editable denomination list appears beneath it. ### 4.7 The gate at payment At a location that verifies cash, something has to stop a cashier from taking money before the drawer has an opening count. The question was where. My first version placed the check at every entry point to an order: 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 in four places. On 18 June I moved it to one place, **the payment page**, for ease of use and a better experience at the counter. The page asks for the opening count when it loads, and again if the cashier presses Complete Order without one. Order-building is never interrupted; cash is asked about at the moment cash is about to be taken (Figure 7). [Figure: Figure 7. Gate placement before and after the 18 June change. Four enforcement points became one place with two triggers.] Moving the gate meant the interrupted action had to survive the interruption: saving the opening count completes the order the cashier was trying to complete, and cancelling returns to the order without a spurious "unsaved changes" warning. Running against a real server also exposed a timing problem. Just after a count was saved, the page's copy of the session state had not refreshed, so it briefly believed there was no session and asked again. The implemented gate remembers the interrupted action and waits while session data is loading instead of deciding on stale information (Figure 8). Such behaviours decide whether a gate feels like a checkpoint or a trap. [Figure: Figure 8. The 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 while session data refreshes, and then completes the order once.] ### 4.8 Corrections and history A cash record that cannot be corrected gets worked around; one that can be corrected silently cannot be trusted. Cashiers mistype. An owner who notices that a 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. Every cash entry (opening balance, closing balance, each cash in and each cash out) therefore keeps its own history. Creating, editing and deleting each leave an entry with the person and the time. Edits show before and after values for each field that changed: the amount, the note and individual denomination counts. An edit requires a note, for the same reason an imbalanced close does. Deleted entries remain visible in the history (Figure 9). Because Nepal is five hours and forty-five minutes ahead of UTC, a quarter-hour slip in reading timestamps would make corrections appear out of order, so the history handles the offset carefully and uses a 24-hour clock. [Figure: Figure 9. The history of one cash movement, newest first. An admin corrects an amount and its note, then deletes the entry; the cashier's original record stays visible underneath. Fictional names and amounts; times in Nepal time, 24-hour.] ### 4.9 Permissions Who may change a drawer follows from the viewer dimension in Section 4.1 (Table 4). | Action | Session owner | Admin | Other staff | |---|---|---|---| | See the drawer and its history | Yes | Yes | Yes | | Add a missing opening balance | Yes | Yes | No | | Edit an opening or closing balance (note required) | Yes | Yes | No | | Add, edit or delete cash in and cash out | Yes | Yes | No | | Close the session | Yes | Yes | No | | Add a closing balance after an easy-mode close | Yes | Yes | No | *Table 4. Actions by viewer. The interface shows or hides actions by role; the backend enforces its own rules, which I did not design.* Other staff see the drawer and its history, but the controls that change it are hidden rather than disabled, which keeps the card readable for a cashier looking at their own drawer. ## 5 Iterations Most of the decisive changes in this project removed something. Each item below was built, used and replaced. The reasons for the gate move and the toggle removal are the ones I gave at the time; the others are my retrospective reading of the changes. [Figure: Figure 10. Things that were built, tried and replaced, with dates.] | Date (2026) | Before | After | Reason | |---|---|---|---| | 2–8 June | Settings and denomination list kept in the browser, so the whole flow could run before the backend existed | Settings stored per location on the server | The prototype had served its purpose; a location policy must be shared by every device at the counter | | 8 June | Separate Opening and Closing cards | One Cash Drawer card in ledger order | Two cards gave two numbers and no explanation; one ledger lets a manager re-add the column (retrospective) | | 8 June | Drag-to-reorder and per-denomination enable checkboxes | A plain list sorted by value, with edit and delete | Order carries no meaning for cash, and a present-but-disabled denomination is two concepts for one outcome (retrospective) | | 10–11 June | Setting labelled "Strict Session Cash", then "Session Cash Management" | "Require Cash Verification", chosen by the CEO | The final label names what staff will have to do, not the mode's internal name | | 12 June | Counts recordable only in strict mode | Counts optional in easy mode too | Easy-mode shops may still want a record, without a gate | | 9–18 June | Gate at four order entry points | Gate on the payment page only | Ease of use; order-building is never interrupted | | 23–24 June | An "Enable Cash Denominations" switch | Removed the next day | Not necessary: denomination counting is available whenever the location has denominations configured | *Table 5. Iterations, with dates and reasons.* Two patterns run through these changes. The first is consolidation: two cards became one ledger, four gates one, two switches one; every extra place had been somewhere for behaviour to diverge. The second is that the most consequential problems concerned timing, not appearance: what the interface shows while the server catches up, and whether an interrupted action survives. These appeared only when the flow ran against a real server, which argues for drawing in-between states in the first sketch. Three things did not change. The blind count, the empty-versus-zero rule and the note-on-mismatch rule were present in the first working version on 2 June and survived every later revision. ## 6 Discussion ### 6.1 Transferable design principles I offer five principles drawn from this case. They are intermediate-level design knowledge in Höök and Löwgren's (2012) sense: more general than this feature, less than a theory, and untested outside it. **P1. Model the money before the screens.** Once opening, movements, closing and difference were separate records with a defined sum, every screen became a view of one ledger. Where a system holds an expected value, it should show the parts of the sum, so a person can check the total rather than trust it. **P2. Collect the independent observation before revealing the reference.** Where a person measures something the system already has an expectation for, sequence the interaction so that the measurement is recorded before the expectation is shown. This is a structural response to anchoring and confirmation (Tversky & Kahneman, 1974; Nickerson, 1998) that does not depend on the person's resolve. Its scope should be stated plainly: in this design it protects the moment of counting, not the whole session. **P3. Give nothing 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 an input field (Codd, 1979; Thimbleby & Cairns, 2010). The same principle separates states from events: a balance can be zero; a movement cannot. **P4. Ask for an explanation only when something is wrong.** A note required at every step becomes noise. A note required only on a mismatch, a manual movement or a correction keeps the request rare and attaches it to the moment where an explanation is most informative. The record keeps the discrepancy instead of netting it away. **P5. Interrupt where the consequence is, and let the interrupted action finish.** The gate became simpler and less disruptive when it moved to the one place where cash is actually taken. Where a system asks matters as much as what it asks, and a gate is acceptable only if completing it resumes what the person was trying to do. One further consideration underlies all five: control should be proportionate. The per-location setting acknowledges that a reconciliation ritual suited to a shop with several cashiers is friction for a sole owner, consistent with the internal-control view that controls should fit the organisation (COSO, 2013). ### 6.2 Comparison with documented competitor patterns For this paper I read the public help documentation of several established POS products in September 2026 (listed after the references). This was a documentation scan, not hands-on testing, and it was not the basis of the original design; Zoho is the only product I can say I consulted at the time. Where documentation is silent I write "not documented", which does not mean a product lacks the feature. | Pattern | Documented elsewhere | This design | |---|---|---| | Opening count, cash in and out, counted against expected | Common to Zoho, Loyverse, Square, Toast, Odoo and Shopify | Same baseline; table stakes, not a contribution | | Reason for a cash movement | Optional notes in Zoho and Shopify; a description in Square; preconfigured payout reasons in Toast; a reason in Odoo | Note required on every movement | | Hiding the expected amount | By permission: Loyverse shows cashiers without a report right only the actual-cash field. Toast's documented close screen does the opposite: it displays the expected balance and offers the pre-calculated amount as the entry. Square's behaviour could not be verified from its public documentation | By sequence: hidden from everyone during the count, shown after it | | Handling a difference at close | Zoho suggests recording a cash in or out to adjust it, and offers approval workflows; Odoo can block a close outside an authorised difference; Toast writes a default comment by variance; notes optional in Shopify | Difference kept visible and filterable as short or excess; note required | | Correction history | Pay in and out history in Square; otherwise not documented | Per entry: created, edited and deleted, with before and after | | Policy scope | Sessions or cash management enabled per device or register | Per-location verification setting | *Table 6. Documented competitor patterns (2026 documentation scan) against this design.* Two observations follow. First, the products that hide the expected amount do so with a *permission*, which makes blindness an organisational choice and can hide the figure from cashiers entirely. This design does it with a *sequence*, which applies to everyone and needs no configuration, but reveals the figure after the count. The two approaches are complementary, and an owner-configurable blind mode for the whole session would combine them. Second, the clearest difference lies in what happens to a discrepancy. Adjusting the drawer with a balancing cash entry makes the numbers agree but removes the discrepancy from view; approval workflows move the explanation to a later step and a different person. Requiring a note at the moment of closing, and keeping the result filterable, preserves the discrepancy as a fact with an owner, a time and a reason. Among the Nepali POS products I checked, none publicly documented a denomination-level count, an expected-versus-counted comparison or a discrepancy rule. That absence is in public documentation only. ## 7 Limitations and proposed evaluation ### 7.1 Limitations The limits of this account are substantial, and I state them once, plainly. - **No field study.** No cashier or owner outside the team was observed, interviewed or tested with before the feature shipped. The only walkthrough with real drawers was an internal hardware demonstration. - **No measured outcomes.** I do not know how many locations use verification, how often drawers close short or in excess, what notes say, or whether closing became faster or slower. The only evidence of use is the CEO's report to me that the feature is live and properly used by real shops, with no usage data behind it. - **A single designer's account.** Written by the person who designed and built the feature, months afterwards, it is subject to the biases of self-report and hindsight; retrospective reasons are marked as such. - **An untested mechanism.** The claim that a blind count reduces bias toward the expected amount rests on general findings about anchoring and confirmation (Tversky & Kahneman, 1974; Nickerson, 1998; Furnham & Boo, 2011), not on evidence from this product. - **Accessibility.** Every status is carried by words as well as colour, but I have not tested the flow with assistive technology. ### 7.2 Proposed evaluation The following study would test the principles in Section 6.1. It is a proposal; none of it has been carried out. **Phase 1: shift-change observation.** Observe at least two shift changes at each of four to six shops, mixing verification on and off and restaurants and retail counters, followed by a short interview with the cashier and the owner. The protocol would record where cashiers hesitate during the count; whether they look for the expected amount before or during counting; what they write in the note when a drawer is not balanced, and how long that takes; whether the payment-page gate ever delays a waiting customer; and how corrections are made and by whom. The aim is qualitative: to find where the design's assumptions fail. **Phase 2: discrepancy logging.** With shop owners' consent, log anonymised session outcomes over a fixed period, for example eight weeks. Every measure needs an explicit denominator and method (Table 7). | Question | Measure | Denominator | Method | |---|---|---|---| | Do people count? | Sessions closed with a count | All closed sessions at verifying locations | Session records | | Are differences visible? | Balanced, short and excess closes, and the size of each difference | Sessions closed with a count | Session records | | Are they explained? | Imbalanced closes whose note gives a reason | Imbalanced closes | Two coders classify notes as reason, placeholder or empty; agreement reported | | Are corrections honest? | Edits per session, and edits made after close | Closed sessions | History records | | Is the gate costly? | Time from the payment page opening to the opening count being saved | Gate prompts shown | Interface timing events | | How is counting done? | Counts by total versus by denomination | Counts entered | Session records | | Does anyone switch off? | Locations that turn verification off | Locations that ever turned it on | Settings history | *Table 7. Proposed discrepancy log. Each measure is reported with its denominator.* The last two rows are guard rails. If the gate slows the counter, owners will switch verification off, and the feature quietly stops existing; that rate is the most important single number. **Phase 3: a controlled test of the blind count.** Field data cannot isolate the effect of hiding the expected amount, because there is no comparison condition. A short task-based study could. Participants with cash-handling experience would count prepared drawers of play notes whose true contents are known, in two conditions: the expected amount hidden until the count is submitted (the shipped design), and the expected amount visible during counting. Some drawers would match the displayed expectation and some would differ from it by a known amount. The primary measure is the absolute error of the submitted count against the true contents; the secondary measure is how often a count matches the displayed expectation when the true contents do not. A within-subjects design with counterbalanced order and different drawer contents in each condition would keep the study small. If P2 holds, the visible condition should show counts pulled toward the displayed figure. ## 8 Conclusion This paper has described the design of cash-session reconciliation for a POS product used by small shops and restaurants in Nepal: from a session that recorded only a time and a person, to an accountable drawer with an opening count, recorded movements, a blind closing count, a required explanation for any mismatch and a visible history of corrections, all behind a per-location policy. It has set out the reasons for each decision and the iterations, most of which removed something, and it has drawn five principles: model the money before the screens; collect the observation before revealing the reference; give nothing two meanings; ask for an explanation only when something is wrong; and interrupt where the consequence is, letting the interrupted action finish. These are offered as precedent and as hypotheses, not findings. The feature is in production, and the CEO reports that real shops use it, but nothing about its effect has been measured. The study in Section 7 is the next step; this account makes the reasoning available for that test and for other designers of systems where people count something and a computer holds the expected answer. ## References Boling, E. (2010). The need for design cases: Disseminating design knowledge. *International Journal of Designs for Learning, 1*(1). https://doi.org/10.14434/ijdl.v1i1.919 Codd, E. F. (1979). Extending the database relational model to capture more meaning. *ACM Transactions on Database Systems, 4*(4), 397–434. https://doi.org/10.1145/320107.320109 Committee of Sponsoring Organizations of the Treadway Commission. (2013). *Internal control: Integrated framework*. COSO. https://www.coso.org/guidance-on-ic Dell, N., & Kumar, N. (2016). The ins and outs of HCI for development. In *Proceedings of the 2016 CHI Conference on Human Factors in Computing Systems* (pp. 2220–2232). ACM. https://doi.org/10.1145/2858036.2858081 Demirgüç-Kunt, A., Klapper, L., Singer, D., & Ansar, S. (2022). *The Global Findex Database 2021: Financial inclusion, digital payments, and resilience in the age of COVID-19*. World Bank. https://doi.org/10.1596/978-1-4648-1897-4 Donner, J., & Escobari, M. X. (2010). A review of evidence on mobile use by micro and small enterprises in developing countries. *Journal of International Development, 22*(5), 641–658. https://doi.org/10.1002/jid.1717 Furnham, A., & Boo, H. C. (2011). A literature review of the anchoring effect. *The Journal of Socio-Economics, 40*(1), 35–42. https://doi.org/10.1016/j.socec.2010.10.008 Gaver, W. (2012). What should we expect from research through design? In *Proceedings of the SIGCHI Conference on Human Factors in Computing Systems* (pp. 937–946). ACM. https://doi.org/10.1145/2207676.2208538 Höök, K., & Löwgren, J. (2012). Strong concepts: Intermediate-level knowledge in interaction design research. *ACM Transactions on Computer-Human Interaction, 19*(3), 1–18. https://doi.org/10.1145/2362364.2362371 Joyce, E. J., & Biddle, G. C. (1981). Anchoring and adjustment in probabilistic inference in auditing. *Journal of Accounting Research, 19*(1), 120–145. https://doi.org/10.2307/2490965 Nickerson, R. S. (1998). Confirmation bias: A ubiquitous phenomenon in many guises. *Review of General Psychology, 2*(2), 175–220. https://doi.org/10.1037/1089-2680.2.2.175 Nielsen, J. (1994). Enhancing the explanatory power of usability heuristics. In *Proceedings of the SIGCHI Conference on Human Factors in Computing Systems* (pp. 152–158). ACM. https://doi.org/10.1145/191666.191729 Perry, M., & Ferreira, J. (2017). Moneywork: Practices of use and social interaction around digital and analog money. *ACM Transactions on Computer-Human Interaction, 24*(6), 1–32. https://doi.org/10.1145/3162082 Schön, D. A. (1983). *The reflective practitioner: How professionals think in action*. Basic Books. Smith, K. M. (2010). Producing the rigorous design case. *International Journal of Designs for Learning, 1*(1). https://doi.org/10.14434/ijdl.v1i1.917 Thimbleby, H., & Cairns, P. (2010). Reducing number entry errors: Solving a widespread, serious problem. *Journal of the Royal Society Interface, 7*(51), 1429–1439. https://doi.org/10.1098/rsif.2010.0112 Toyama, K. (2010). Human–computer interaction and global development. *Foundations and Trends in Human–Computer Interaction, 4*(1), 1–79. https://doi.org/10.1561/1100000021 Tversky, A., & Kahneman, D. (1974). Judgment under uncertainty: Heuristics and biases. *Science, 185*(4157), 1124–1131. https://doi.org/10.1126/science.185.4157.1124 Zimmerman, J., Forlizzi, J., & Evenson, S. (2007). Research through design as a method for interaction design research in HCI. In *Proceedings of the SIGCHI Conference on Human Factors in Computing Systems* (pp. 493–502). ACM. https://doi.org/10.1145/1240624.1240704 ### Product documentation consulted (2026 documentation scan) Read in September 2026 for Section 6.2 only. Apart from Zoho, none of these informed the original design. - Zoho, Retail Store sessions: https://www.zoho.com/en-in/erp/help/sales-channel/retail-store/sessions/manage-sessions.html and https://www.zoho.com/en-in/erp/help/sales-channel/retail-store/sessions/session-approvals.html - Loyverse, shift management: https://help.loyverse.com/help/shift-management-loyverse-pos - Square, cash drawer management: https://squareup.com/help/us/en/article/5152-cash-drawer-management and https://squareup.com/help/us/en/article/8344-start-and-end-a-cash-drawer-session - Toast, cash drawer operations: https://doc.toasttab.com/doc/platformguide/adminCashDrawerPOSOperations.html - Odoo 18, Point of Sale: https://www.odoo.com/documentation/18.0/applications/sales/point_of_sale.html - Shopify POS, register sessions: https://help.shopify.com/en/manual/sell-in-person/shopify-pos/cash-register-management/register-sessions-in-shopify-pos # Research overview URL: https://www.bibhushansaakha.com.np/research # Designing for disclosure and for daily operations ## Research statement I am a UI/UX designer and frontend engineer in Kathmandu. I work on Tigg, an accounting and point-of-sale product built by BIC Technology for businesses in Nepal, and before that I led Myra, a student women's-health venture. Two questions keep coming back in that work, and they are what I want to study. ### 1. HCI for health, privacy and consent When a health app lets someone share, the share button is a consent system. Who can see what, for how long, and what does the other person learn when access is taken away? In Myra, a two-sided survey of 171 people showed that young women in Nepal would accept support from a partner or family member, but mostly on the condition of control, and that the men who would receive that information wanted interpretations rather than raw data. I want to study how people understand and negotiate visibility of sensitive data in households and relationships, especially where stigma (such as menstrual restriction) means the phone itself is shared space. ### 2. Operational software in low-resource and emerging-market contexts Most of my professional work is operational software: cash drawers, stock with batches and serial numbers, spreadsheet imports, backups, multi-currency accounting. In Nepal these tools meet Nepali dates, VAT rules, low-end devices and shop routines that global products don't design for. The recurring pattern I see is that the interface is only half the task. The other half happens on paper, in a drawer, at a printer or in someone's memory, and the software has to be designed around that. I am interested in methods for studying and designing these hand-offs between screen and world, with limited budgets and without formal user research infrastructure. ### How I work, and its limits I design and build, so my evidence usually comes from what shipped, from teammates and from first-party feedback, not from controlled studies. The Myra survey below is my only primary dataset. I present it as a compact study, with its limits stated, because I would rather show a small study honestly than a large claim. ## Study: Cycle tracking, stigma and consent-first sharing (Myra survey, 2025) ### Research question Would young women in Nepal let a partner or family member follow their menstrual cycle through an app, and if so, on what terms? And what would the people receiving that information want from it? Two sub-questions supported the design: how do women currently track, and what do they experience that tracking misses? ### Method | | | |---|---| | **Instrument** | An online questionnaire written by the Myra team, on a Tally form. Sections on tracking habits, symptom frequency (0–5), emotion and restriction, Companion Mode, help channels, a full ranking of five features, and price | | **Branches** | Gender-branched. Women answered about tracking and sharing; men answered about following and what they would want to receive | | **Recruitment** | The team's networks: friends and colleagues, email, Instagram, LinkedIn and student organisations | | **Fielding window** | April–May 2025 | | **Responses** | n = 171: women's branch 106 (105 women, 1 non-binary respondent), men's branch 65 | | **Analysis** | Descriptive counts and shares; one crosstab (sharing comfort × "prefer to manage it alone"); free-text answers grouped into themes afterwards, for this write-up (one reader, no second rater) | | **Interviews** | Alongside the survey we held informal interviews and conversations; I didn't keep notes, so findings here come from the survey | ### Sample and limits [Figure: Sample profile, n = 171; panel C n = 80 who said how they found the survey. Convenience sample, network-recruited, student-heavy, 18–24 skew; not representative of women in Nepal.] This is a **convenience sample, recruited through our own networks and heavy with students**. 87% were aged 18–24 and no one was over 34. Only 3.5% preferred Nepali alone. Half of those who said how they found the survey came through a friend, colleague or team member. It is **not representative of women in Nepal**, and every number below describes this sample only. Data cleaning: one row shifted by a column was realigned; one exact duplicate pair is retained and noted. Only aggregates are published; no individual answers are shown. ## Findings ### F1. Women track the date, not the experience [Figure: Women's branch, n = 106. Convenience sample, student-heavy, 18–24 skew; not representative.] 61% of women track with an app (more than half of them with Flo), and 88% track the date. But under a third track mood (29%) or pain (31%), although 65% report mood swings 4–5 on a 0–5 frequency scale and 70% rate their interest in mood tracking at 4–5. ### F2. Stigma is part of the context 75% of women had been restricted from doing something because of their period. 74% associate irritation with their cycle and 45% sadness; 3% chose empowerment. Restricted respondents rated the openness of discussion at home lower (mean 3.30 vs 4.13; scale direction inferred, see threats to validity). ### F3. Sharing is conditional, and control is the swing vote [Figure: Women n = 106, men n = 65. Convenience sample, network-recruited, student-heavy, 18–24 skew; not representative. The men's 'want to receive' item was an untitled form field; options shown as published.] | Women: comfortable if a partner or family member could follow your cycle? | n | % | |---|---|---| | Yes, definitely | 52 | 49.1 | | Maybe, if I could control what they see | 34 | 32.1 | | No, not comfortable | 11 | 10.4 | | Not sure | 9 | 8.5 | 37% of women also ticked "I prefer to manage it alone", including 10 of the 52 who said "yes, definitely". Wanting support and wanting privacy were not opposites for these respondents. ### F4. Receivers want interpretation, not a feed 86% of men rated knowing her physical symptoms, and knowing when she needs rest, at 4–5 in importance. Only 11% wanted "symptom updates"; 63% wanted a daily summary and 51% emotional-support tips. 52% said reminders to be emotionally available should come only if she chooses to share. ### F5. Companions are family, not only partners [Figure: Women n = 106, men n = 65. Same sample limits.] Men would use the feature for a mother (58%) or sister (54%) almost as often as for a girlfriend (66%). ### F6. Priorities and price [Figure: Feature ranking n = 106; help channel n = 105. Same sample limits.] Nutrition and fitness ranked first of five features; community support last. Pregnancy tracking was polarised (26 first, 28 last). 58% wanted to talk to a doctor in the app, but only 27% would pay for telehealth, and 52% chose only "a completely free app". Among the 70 who named a positive monthly amount in free text, the median was NPR 225. ## What changed in the design The findings turned a broad product pitch into one consent model, which I designed as a concept: | Finding | Design change | |---|---| | F3: sharing is conditional on control | Every grant starts off and is chosen item by item; she can stop at any time and the other person is not told why | | F4: receivers want interpretation | The companion sees a derived signal ("she may need more rest this week") and one suggested action, never her logs | | F5: companions are family too | Several companions, each with separate grants, chosen by relationship | | F1: mood is experienced but not tracked | Mood is logged beside flow and pain in the same quick log | | F2: restriction happens at home | Discreet notification copy by default; anonymous mode blocks sharing | | F6: doctor access wanted, but few would pay | Clinician access framed as an explicit service around a free core | Two things the data did **not** support also changed the plan: a Nepali-by-default interface (language is now the first choice at onboarding instead), and a later pregnancy-first direction, which this sample cannot speak to. The full design reasoning, including the states of a companion link, is in the [Myra case study](/work/myra). None of it was built or tested with users. ## Reflection and threats to validity - **Sampling.** A convenience sample from our own networks, young and English-comfortable. The results describe people like the team's contacts. Selection bias probably favours people who are open to talking about periods and to new apps. - **Stated vs actual behaviour.** A survey measures what people say they would do. Saying "maybe, if I control it" is not the same as linking a sister's phone. - **Instrument.** The team wrote the questionnaire, and I have no record of a pilot. Two men's-branch fields were untitled on the form, so I report those options as published and don't interpret the scale. A device question was added late (21 answers) and I don't use it. The openness scale had no exported labels, so its direction is inferred from the data. - **Framing.** The questions introduced Companion Mode by name and described it as respectful, which may have raised acceptance. - **Coding.** Free text (price, "what's missing") was grouped into themes after the study, for this write-up, by one reader with no second rater, so those numbers are soft. - **Interviews.** Held informally and not documented, so they cannot be used as evidence here. - **My position.** I was the founder analysing demand for my own product. I tried to counter that by publishing the counts that cut against us (37% would rather manage alone; pregnancy tracking ranked last by 28 women) alongside the ones that helped. ## What I would do next 1. **A proper interview study.** Semi-structured interviews with women across age, language and household type, including Nepali-only speakers and women outside Kathmandu, with consent, recorded notes and a documented coding process. The goal: how sharing is negotiated in real households, not only whether it is wanted. 2. **A consent-flow usability test.** 5–8 moderated sessions on a clickable permissions prototype. The main measure is whether a participant can correctly explain what a companion will see after setup, and whether they can stop sharing without help. 3. **Paired observation, later.** Only if the first two go well: a short diary study with linked pairs, measuring what support actually happens and whether anyone reports harm. ## Other inquiry Smaller design questions from my other work, each written up as a case: - [**Designing for physical counting**](/work/cash-sessions): a cash-drawer flow in which the cashier counts before seeing the expected amount, so the count can't drift toward the target. - [**Learning modes**](/work/kati-sajilo): one question bank with three levels of help (Learn, Practice, Review) in an exam-prep app I built for myself and my friends. - [**Privacy display modes**](/work/dime): hiding amounts in a personal finance visualiser, and finding the places where they still leaked through. ## Cite this > Saakha, B. (2026). *Cycle tracking, stigma and consent-first sharing: a survey study for the Myra concept (n = 171), Nepal, 2025.* Retrieved from https://bibhushansaakha.com.np/research Aggregates only. The respondent-level data is not published. # Blog: Writing honest case studies when nothing was measured URL: https://www.bibhushansaakha.com.np/blog/honest-case-studies · Published 2026-10-02 When nothing was measured, an honest UX case study says so once, plainly, and then labels precisely what did happen: handed off, merged, released, reported in use. Commits and pull requests are activity, not outcomes. A clear status vocabulary, dated and sourced, makes a product design portfolio more credible than an invented percentage, because a reviewer can check every claim against what you're actually showing. None of my case studies has a measured outcome. That isn't unusual for a designer at a small product company with no analytics on the features I worked on. What follows is how I write about that, and the rules I set myself for this site. ## Why most UX case studies overclaim The usual template asks for "Impact" at the end, and there's pressure to fill it. So people write "improved efficiency by 40%" with no baseline, method or time window. Reviewers have seen hundreds of these. A round, high number that doesn't connect to the design shown reads as a red flag, not a result. The quieter overclaim is the verb. "Shipped" can mean a Figma page marked ready, code on a branch, or a feature every customer uses daily. "I built" can mean I built it, or I designed it and an engineer built it. These blur together, and the reader can't tell which you mean. ## Why commits aren't outcomes As a designer who also writes frontend code, I'm tempted to count things the repository can count: commits, merged changes, lines. They're real, and they're easy to chart. But they answer the wrong question. - **They measure effort, not effect.** Ten commits can be ten fixes for one bad decision. - **They reward noise.** Splitting work into smaller commits doubles the number and changes nothing for users. - **They leak.** Commit messages, branch names and ticket numbers are internal detail about how an employer builds software. So I don't show commit or pull-request counts anywhere, and I don't put ticket numbers, hashes or release tag strings on the site. I say "a tagged release in September 2026", which tells a reader what they need. ## A status vocabulary for design portfolios Every case on this site carries one or more of these labels, each with a date and, where it matters, a source. | Label | Means | Doesn't mean | |---|---|---| | **Design handoff** | The design was marked ready for development or handed to an engineer | Built, reviewed or tested | | **Merged to production** | The code was merged into the production line, on a date | Every customer has it, or uses it | | **Tagged release** | It's included in a named release | Adoption, or anyone noticing | | **In use (first-party report)** | Someone inside the company told me it's being used, and I say who | Independent verification or a number | | **Measured outcome** | A metric with a method, a period and a denominator | Anything else. "None" is a valid entry | Two more labels sit outside that ladder: *Concept, paused* for work that never became a product, and *Work in progress* for designs still being explored. The labels stack, and none implies the next. [Bulk Import](/work/bulk-import) has a Design handoff before June 2026, then Tagged releases in July and August, built by the senior frontend engineer. [Cash sessions](/work/cash-sessions) has Merged to production in June 2026 and In use (first-party report). [Myra](/work/myra) is a concept, paused: no app was released. ## How to write "Measured outcome: none" Once, in the results section, without apology. Then do the three things that make the absence useful. 1. **Say what you do have, and where it came from.** For cash sessions, the feedback I can cite came through one channel: Properly used by real shops. That's real, and it's useful, but it's informal and second-hand, so it's labelled as exactly that and nothing more. 2. **Make the claim as narrow as the evidence.** For Bulk Import I don't write "removed the trip back to Excel". I write that *supported* errors are repaired in the product before posting, and that some problems still start in the file. 3. **Name what you'd measure first, and why.** For Bulk Import: whether the same file is uploaded again within a day, because that's the proxy for the main claim. For the [backup](/work/backup) page: how often people leave and come back, and how many emailed links expire unused. Research reviewers read this as method; hiring managers read it as product sense. A reviewer can't check your 40%. They can check that your status label matches your screenshots, that your role matches your verbs, and that your limits are stated where the claims are. Those checks are what build trust. ## How do you show employer work in a UX portfolio? ### Ask, and agree the scope I asked first. BIC Technology, the company behind Tigg, allowed product screens. That permission didn't extend to everything else that comes with a codebase, so I set the boundary myself: product behaviour, UI states, microcopy and design reasoning are on the site; ticket numbers, code, file paths, internal tool names and anything that would help a competitor are not. Teammates appear by role: the CEO, the senior frontend engineer, QA. ### Reconstruct instead of blurring Where I don't yet have a clean screenshot, I redraw the screen with fictional data and label it as a reconstruction: "the old validation screen, redrawn; the labels are the real ones; the file name, counts and messages are fictional." Advice differs here. Nielsen Norman Group lists blurring identifying details as one option, alongside showing process and recreating designs generically. The Interaction Design Foundation advises against blacking out parts of a case study, because it can still breach an agreement and it hurts credibility. I side with reconstruction: it's clearer to read and safer to publish. ### Label every image Every figure on this site has a provenance: screenshot, Figma, reconstruction, exploration, diagram, chart or photo. An exploration is never presented as the final design, and a reconstruction is never presented as a screenshot. [Figure: An example of labelling: archived explorations from the Bulk Import design, and what became of each. The notes describe the built product, not a claim that the build reused the archive.] ## Credit: use exact verbs The most common credibility problem in portfolios is unclear contribution. I use three verbs and stick to them: - **"I designed and built"**: cash sessions on web, the POS side of batch and serial tracking, the backup page. - **"I designed; the senior frontend engineer built"**: Bulk Import. Decisions my frames didn't cover are credited to them by name of role. - **"Proposed, not shipped"**: the next steps at the end of each case. Being the only designer at Tigg makes this easier to say and more important to say carefully. I write about that role in [being the only designer on a product team](/blog/only-designer-on-the-team). ## A checklist for honest case study writing - [ ] A status label on every case, with a date and a source. - [ ] "Measured outcome: none" stated once, where results go. - [ ] Feedback quoted with its channel ("reported to me by the CEO"). - [ ] No commit, PR or ticket counts as evidence. - [ ] Exact verbs for who designed and who built. - [ ] Permission obtained, and its scope respected. - [ ] Every image labelled; reconstructions use fictional data. - [ ] Research numbers shown with their sample and limits (see [consent-first sharing](/blog/consent-first-sharing-health-apps)). - [ ] A "what I'd measure" line for every claim you can't prove. - [ ] No invented interviews, participants or quotes, ever. ## Does this make a portfolio weaker? I don't think so. The cases read smaller than they might, and the claims are easier to defend in an interview. For graduate admissions, where readers look closely at method, an honest limits section is evidence of the research habit they're looking for. My [research page](/research) is written the same way. ## References - Rachel Krause, [5 Steps to Creating a UX-Design Portfolio](https://www.nngroup.com/articles/ux-design-portfolios/), Nielsen Norman Group. - Yu Siang Teo, [How to Handle Non-Disclosure Agreements (NDAs) When You Write Your UX Case Study](https://ixdf.org/literature/article/how-to-handle-non-disclosure-agreements-ndas-when-you-write-your-ux-case-study), Interaction Design Foundation. # Blog: Consent-first sharing in health apps URL: https://www.bibhushansaakha.com.np/blog/consent-first-sharing-health-apps · Published 2026-10-01 Sharing in a health app should be off by default, granted one item at a time, and revocable silently, because for most people sharing is conditional on control. In a 171-response survey for Myra, a women's-health and period-tracker concept I led in Nepal, only about half of women said yes outright to a partner or family member following their cycle; another third said *maybe, if I could control what they see*. That result turned a "share with partner" feature into a consent system, and it's the clearest piece of research behind my work on consent UX and health app privacy. Two things up front. **Myra is a paused concept, not a released app**, so nothing here was used or tested. And the survey is a small, skewed sample, so every number below comes with its limits beside it. The full study is on my [research page](/research), and the design is in the [Myra case study](/work/myra). ## What did the survey ask, and who answered? We fielded an online survey on a Tally form in April–May 2025. The team wrote the questionnaire. It branched on gender: women answered about tracking and sharing, men answered about following. | | | |---|---| | **Responses** | n = 171: women's branch 106 (105 women, 1 non-binary respondent), men's branch 65 | | **Recruitment** | Our own networks: friends, colleagues, email, Instagram, LinkedIn, student organisations | | **Who** | Student-heavy; 87% aged 18–24, no one over 34; only 3.5% preferred Nepali alone | | **Analysis** | Descriptive counts and shares, one crosstab; only aggregates are published | Alongside the survey we held informal interviews and conversations. I didn't keep notes, so nothing here relies on them. This is a **convenience sample**, network-recruited, student-heavy and skewed to ages 18–24. It is **not representative** of women in Nepal. A survey also measures what people say they would do, not what they'd do with a real feature. The numbers describe young, connected, mostly English-speaking respondents, and nothing more. ## Would women share their cycle data with a partner or family? The headline result, from the women's branch (n = 106): | Comfortable if a partner or family member could follow your cycle? | n | % | |---|---|---| | Yes, definitely | 52 | 49 | | Maybe, if I could control what they see | 34 | 32 | | No, not comfortable | 11 | 10 | | Not sure | 9 | 9 | Then the detail that changed the design: 37% of women also ticked "I prefer to manage it alone", **including 10 of the 52** who said "yes, definitely". For these respondents, wanting support and wanting privacy weren't opposites. Control was the swing vote. [Figure: The two-sided result. Women n = 106, men n = 65. Convenience sample, network-recruited, student-heavy, 18–24 skew; not representative. The men's 'what would you want to receive' item was an untitled field on the form; options are shown as published.] ## What does the other person actually want to see? The men's branch (n = 65, same sample limits) gave the other half of the answer. 86% rated knowing her physical symptoms, and knowing when she needs rest, at 4–5 in importance. But only **11% wanted "symptom updates"**. They asked for interpreted outputs instead: a daily summary (63%) and emotional-support tips (51%). And 52% said reminders to be emotionally available should come *only if she chooses to share that info*. So the people receiving the data wanted to understand, not to watch. That's convenient, because it's also the safer design. ## Why sharing should be off by default and scoped Put the two branches together and the requirements write themselves: 1. **Off by default.** Every grant starts off. A third of women would share only with control; the default has to respect the most cautious answer, not the most enthusiastic one. 2. **Scoped per item.** She grants one thing at a time: a needs-rest signal, period dates, a mood summary. Not "share my data". 3. **Interpretations, not logs.** The companion sees "She may need more rest this week" and one suggested action, never her calendar, symptoms or notes. 4. **Several people, separate grants.** Men in the sample would use the feature for a mother (58%) or sister (54%) almost as often as a girlfriend (66%). A companion is a relationship, and there can be more than one. 5. **Silent revocation.** She can stop in two taps. The other side sees a neutral "Sharing paused", never a reason. [Figure: Relationships and consent. Women n = 106, men n = 65. Convenience sample, network-recruited, student-heavy, 18–24 skew; not representative.] The proposed defaults, as a table: | Data | Default | Can she grant it? | |---|---|---| | Needs-rest signal (derived) | Off | Yes, recommended first | | Period dates and current phase | Off | Yes | | Mood summary (weekly, derived) | Off | Yes | | Raw symptom logs | Off | One symptom at a time, with a warning | | Fertility window | Off | Yes, with a second confirmation | | Private notes | Never | No | The survey justifies the direction of these defaults. It doesn't validate their details. 75% of women in the sample (n = 106) had been restricted from doing something because of their period. Restriction happens at home, where other people pick up the phone. So privacy by design here isn't only about servers. It's about what the lock screen shows and what a family member can learn from an app. The concept uses generic notification copy by default for that reason. This also lines up with a principle that exists in law elsewhere. The EU's GDPR asks that, by default, personal data is not made accessible to an indefinite number of people without the person's own intervention. Nepal isn't bound by the GDPR, and Myra wasn't designed to comply with it, but "nothing is shared until she acts" is the same idea. ## How does a consent-first sharing flow work? The flow I designed has six states: no companion, code issued, pending her confirmation, linked with grants, the companion's view, and revoked. - **Inviting isn't sharing.** She gets a short, single-use code. The invite screen says nothing is shared yet. - **Entering a code isn't enough.** She sees who entered it and confirms. A code shared under pressure opens nothing by itself. - **Reducing access is always silent.** Removing one grant, stopping entirely or turning on anonymous mode all show the companion the same neutral state. They never learn *what* was removed or *why*. [Figure: Companion link, two lanes. Every transition that reduces access is silent on the companion's side. Concept, not built or tested.] The silent-revocation rule matters most. In a relationship or a household, "she turned off sharing your access to her mood" is information. A neutral state is the only one that doesn't create a conversation she didn't choose. ## A consent UX checklist for health and femtech apps - [ ] Every sharing grant starts off. - [ ] Grants are per item, not all-or-nothing. - [ ] Share derived signals before raw data; some data (private notes) can never be shared. - [ ] Each companion has separate grants. - [ ] Linking needs the data owner's confirmation, not just a code. - [ ] Stopping takes two taps, works at once, and gives no reason on the other side. - [ ] Lock-screen notifications are generic by default. - [ ] A life-stage change (such as pregnancy) asks again before anything new is shared. - [ ] Every companion screen says who controls it: "She controls what you see". ## What this research can't tell you The survey can't say what a mother in a joint family in a rural district would want, and it can't say whether anyone would actually link a sister's phone. The questions also introduced Companion Mode by name and described it respectfully, which may have raised acceptance. I was the founder analysing demand for my own product, so I've tried to publish the numbers that cut against us too: 37% would rather manage alone, and 10% weren't comfortable at all. The next steps I'd take are a proper interview study with women across age, language and household type, with recorded notes, and a consent-flow usability test where the measure is simple: can a participant correctly explain what a companion will see? ## Where Myra stands The pitch won recognition: **2nd Runner-Up at the Hult Prize at Kathmandu University in 2025**, then selection for the first Hult Prize national competition in Nepal, and **first prize at Project YuwaXcel in 2026**. Then we paused, because we'd lost direction and all of us were working full-time jobs. Measured outcome: none. No app was released, and the consent model was never tested with a single linked pair. What I kept is the lesson: a share button is a consent system. For more on how I label what was and wasn't measured, see [writing honest case studies](/blog/honest-case-studies). ## References - [Article 25 GDPR, Data protection by design and by default](https://gdpr-info.eu/art-25-gdpr/), Regulation (EU) 2016/679. - Hult Prize at Kathmandu University, [result announcement](https://www.instagram.com/p/DEpV555zpvC/) (Instagram). # Blog: A state checklist for business software URL: https://www.bibhushansaakha.com.np/blog/state-design-checklist · Published 2026-10-01 Before I call a screen done, I check it against a list of states: empty, zero, loading, partial, error, denied, expired, interrupted and irreversible. Business software is used on bad days: with half the data loaded, by someone without permission, after the connection dropped. A design that only covers the full, happy screen is a third of a design. This post is the UI states checklist I use, with an example of each from work I've shipped or handed off. I'm the only UI/UX designer at Tigg, an accounting and POS product for businesses in Nepal, and I build much of the frontend too. That means I meet the missing states twice: once in Figma, and again in the browser when the real data arrives. The checklist exists so I meet them the first time. ## Why design for edge cases and states at all? Because the happy path is the state people spend the least time worrying about. They remember the moment they got stuck: the code that didn't arrive, the backup that "failed" while the server was still working, the error that pointed to row 22 of a file they no longer had open. Every state below needs three things: 1. **Wording** that says what is true right now. 2. **A way forward or back**, never a dead end. 3. **A rule** for how the state is entered and left, so engineering and QA can test it. If I can't write all three, the state isn't designed yet. ## The UI states checklist | State | Question to ask | One-line test | |---|---|---| | Empty (first use) | Does it teach the next step? | There's a sentence and an action, not a blank box | | Empty (filtered) | Does it say the filter caused it? | "No records found" sits beside a way to clear the filter | | Zero | Is a confirmed 0 different from blank? | A blank can't be saved as 0 | | Loading / waiting | Does the person know what they're waiting for? | Buttons are disabled while a request runs; the wait has a name | | Partial | Does one failure blank the whole screen? | Only the failed part retries | | Error | Is the error next to its cause, with a fix? | The message names the field, row or cell | | Denied | Can someone learn why they can't act? | A missing permission is explained, not just absent | | Expired | Is old access ended clearly? | The expired link says so, and offers a way back | | Interrupted | What happens on reload, leave or disconnect? | The task resumes or is reported, never silently lost | | Invalid transition | Is the blocked step explained where it happens? | The person stays put and sees why | | Irreversible | Does the confirmation repeat what will be lost? | It names the item and says it can't be undone | | Switched off | What if a setting removes this feature? | Filters, fields and permissions tied to it disappear cleanly | The rest of this post walks through the ones teams skip most often. ## Empty states: three kinds, three messages "Empty" is not one state. In Tigg's [Bulk Import](/work/bulk-import) design there are at least three: "No import history" in recent imports (nothing has happened yet), "No fields mapped yet" on the mapping page (the task has started), and "No records found" in a filtered grid (the data exists but the filter hides it). Each needs different words, and only the last needs a clear-filter control. Sometimes empty is a perfectly normal state, not a gap. In the Myra concept, a woman with no companion linked sees "No one is following your cycle. That's fine." Using the app alone is the default, so the screen shouldn't nag her to fill it. ## Empty vs zero: the state that breaks money In a POS cash count, a blank field and a 0 mean opposite things. Blank means the cashier forgot to enter something; 0 means there is no cash. In the [cash sessions](/work/cash-sessions) flow, a blank opening count is blocked ("Enter an opening amount."), while a 0 is accepted and stays visible, so the cashier can see what they confirmed. A cash-in of 0 is blocked for a different reason: a movement of nothing isn't a movement. I wrote a whole post on this: [empty is not zero](/blog/empty-is-not-zero). The short rule: never let an input turn a blank into 0 on the way to the server. ## Loading and waiting: give invisible waits a name The worst waits are the ones the person can't see. In the [sign-in](/work/sign-in) flow, the browser needs a moment to identify the device before verification can work. Until it has, the button reads "Preparing..." and can't be pressed, so an early click doesn't fail with a confusing error. The emailed code is the second invisible wait, so the code screen says where the code went and shows "Resend in 00:45". For long jobs, a spinner is a promise you can't keep. A full company [backup](/work/backup) can take minutes, and the page can't honestly say how far along it is for much of that time. The lesson I took from it: show that work is happening without claiming how much. ## Partial and error states: fail small, and near the cause A screen built from several requests can fail in pieces. On the cash drawer card, if some details fail to load, the card says "Couldn't load some cash drawer details." with Retry, and only the failed parts are fetched again. One slow request shouldn't blank the whole card. Errors belong next to their cause. A wrong verification code clears all six boxes and puts the cursor back in the first one. A serial number the server refuses shows its reason under the serial field, and is never added to the line. [Figure: The serial-number field in the POS has thirteen states: a scan loop of four, four rejections, and five set from outside the field. Rejected entries are never added to the line.] ## Denied: hiding is not explaining On the cash drawer card, people who don't own a session and aren't admins can see the drawer, but the edit and close controls aren't shown. Hiding 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 reason. A line like "Only the session owner or an admin can change this drawer" fixes that. It's on my list of next changes, and I mention it because a checklist is only useful if you admit where your own screens miss it. A related trap is confusing a switched-off feature with a missing permission. When I designed a permission editor, I wrote copy to separate them: "This is an organization setting, not a permission on the user you are editing." ## Expired and interrupted: design around leaving People reload, switch tabs, close laptops and lose the connection. Assume all of it. - **Interrupted.** The backup page remembers the running job, and returning shows "Reconnecting to backup status…". Bulk import saves the grid as a draft when you leave, and unfinished imports can be reopened. - **Expired.** An old backup link says "This backup link has expired." with a way back. Backups expire after five days, and the page says so above the history table, because rows that vanish without warning look like lost data. - **Expired stock.** In [batch and serial tracking](/work/batch-serial), choosing an expired batch is held and confirmed ("Batch Already Expired"), and cancelling restores the previous value, so the screen never shows a batch the line doesn't have. [Figure: The backup page's states as shipped. Transient failures are hidden on purpose; the dashed states are rarer endings that need clearer messages.] ## Invalid transitions and irreversible actions When a step is blocked, keep the person where they are and say why. The mapping page in Bulk Import blocks Next while required fields are unmapped, turns those selects red and names them in the message. In the POS, saving an order with an incomplete serial line opens that exact line with focus in the serial field, instead of showing a toast that says "something is missing". For irreversible actions, repeat what will be lost. Deleting a backup names it ("Backup from 12-03-2026 10:14:05"), because rows look alike. Posting an import says it "will create actual transactions and cannot be undone". ## How I use the checklist in practice - **In Figma**, each state that a person can actually reach gets its own frame with its own name. "Required still unmapped but clicked next" is a real frame name, and it was the most useful frame on its page. - **At handoff**, I list states with how they're entered and left. Engineers can build from a table; they can't build from "handle errors nicely". - **In QA**, the table becomes the test list. - **Afterwards**, I write down the states I missed. That's why each lead case study on this site has a chapter on states and edge cases. I don't run every screen through every row. A settings page rarely has an "expired" state. But asking the question takes seconds, and the answer is often "yes, and we didn't draw it". ## References - Kate Kaplan, [Designing Empty States in Complex Applications: 3 Guidelines](https://www.nngroup.com/articles/empty-state-interface-design/), Nielsen Norman Group. - Tim Neusesser and Evan Sunwall, [Error-Message Guidelines](https://www.nngroup.com/articles/error-message-guidelines/), Nielsen Norman Group. # Blog: Fix import errors where they happen URL: https://www.bibhushansaakha.com.np/blog/fix-import-errors-where-they-happen · Published 2026-09-30 Most spreadsheet imports fail the same way: you upload a file, the product prints a list of row numbers with errors, and the only way forward is to fix the file in Excel and upload it again. A better bulk import UX pattern keeps repair inside the product: upload once, map your own columns with the gaps shown on the field, then fix, filter and bulk-edit rows in a grid before anything is posted. That's the pattern I designed for Bulk Import in Tigg, an accounting product for businesses in Nepal. I designed it; I didn't build it. The senior frontend engineer at BIC Technology built the whole frontend, and the backend team built the service behind it. The full story is in the [Bulk Import case study](/work/bulk-import). This post pulls out the pattern, because it applies to CSV import design in any B2B SaaS product, not only accounting. ## Why do CSV imports fail with a list of row numbers? Tigg's old importer worked like many others. You downloaded a template, pasted your data into it, uploaded it, and got a screen like *38 records validated, 4 records have errors*, followed by lines such as "Row: 22 Warehouse not found". The buttons were **Confirm Upload** and **Reupload New File**. [Figure: The old validation screen, redrawn. The labels and the row-message format are the real ones; the file name, counts and messages are fictional.] The messages weren't the real problem. The file was the only place anything could be fixed: leave the product, find the row, guess what "not found" meant, save, re-upload, wait. Looked at closely, "bad import UX" had five separate causes: | Cause | What it does to people | What the design needs | |---|---|---| | Fixed header contract | A sheet headed "Qty" or "Godown" fails, or has to be reworked first | Map *their* columns, with suggestions | | Errors cut off from data | A list keyed by row number; the value is in another program | Put each error on its cell | | No partial progress | Nothing survives between attempts | An import you can leave and resume | | No repair tools | One wrong warehouse across 40 rows is 40 edits | Select, filter and change many rows at once | | Hidden, settings-dependent rules | Required fields change per organisation; a template can't show that | Show what is required *here*, while mapping | Better error copy fixes none of these. Moving the repair fixes most of them. ## The data import UX pattern: map, validate, repair, post [Figure: The two loops. Before: every correction goes through Excel and a full re-upload (dashed = outside the product). After: the file is uploaded once and supported errors are fixed inside it.] I think of an import as a session with a lifecycle, not a file transfer: upload once (a template is offered, not required), map, validate on the cell, repair in a grid, then post whole documents. ### Step 1: show the gap on the field during mapping The mapping page has two panels, **Mapped** and **Unmapped**, each with a count. Beside each match sits a sample of the sheet's values, so people can check a match by content, not only by heading. On the first visit the matches are already suggested: a column called "Godown" lands on Warehouse Name without anyone typing. Required fields that are still unmapped are amber. And people do press Next too early; that state is reached in real use. I could let people through and flag every row in the grid, or stop them at mapping. I chose to stop them and show the gap where it is: the select turns red, and the message names the fields ("Please map required fields: Quantity, VAT Applicable"). One missing mapping stays one visible fix, instead of an error on all 42 rows. This matters more when required fields aren't fixed. In Tigg, billing locations, warehouses and automatic product codes switch fields on and off per organisation. The same template can be complete for one business and incomplete for another, so the mapping page, not the template, has to say what is missing. ### Step 2: put each error on its cell Once mapping passes, the rows open in a full-width grid. Every cell has a type: text, number, date, or a searchable dropdown for customer, product or warehouse. Free text is where import errors come from; a typed editor prevents an error instead of reporting it. A wrong value is tinted and carries its own message. This is one of the oldest error-message guidelines, and it's worth repeating: show the message close to the error's source. [Figure: The review grid, redrawn with fictional data. Two checkbox columns separate 'select this document' from 'select this row'. Each cell edits by type, and the error sits on its cell.] ### Step 3: filter by error and fix many rows at once Errors in imports repeat. If a distributor's sheet spells a warehouse "Main Godam" on eight rows, nobody should edit eight cells. The design has one control that counts and filters at once (*Total rows*, *Valid*, *Errors*), a filter for "errors in this column", and a bulk edit bar that states its scope first: *Bulk edit (8 rows): column, value, Apply*. [Figure: Rows Selected → Bulk Update, with fictional counts. Filter to errors in Warehouse Name, select all, set the value once, apply. The rows are checked again and the error count drops to zero.] ### Step 4: post documents, not rows A delivery note with three items is three lines in the sheet but one document in the books. In the build, the senior frontend engineer made posting stop if only some rows of a document are selected, and name that document. Half a document in the books is worse than none. Posting also asks for confirmation, because it "will create actual transactions and cannot be undone". ## How do other import tools handle errors? A 2026 comparison I compared the pattern with other products **in September 2026, after the design**, from public help-centre documentation only. I didn't use any of them hands-on, and a feature missing below may exist without public documentation. This is not what I studied at the time. | Product | Mapping | Fixing errors | |---|---|---| | Zoho Inventory | Auto-mapped, with an option to save the selections for future imports | A preview counts ready, skipped and unmapped records; fix the file and re-import | | QuickBooks Online | A dropdown per field | Invalid cells are highlighted in red in the review and corrected there; 1,000 rows at a time; an import can't be undone | | Odoo 19 | Suggested automatically, with a manual override | A Test step checks the data before the real import | | TallyPrime | Mapping templates | An exceptions report to identify and correct issues before completing | | Flatfile (import tool) | Suggested matches | Edit any cell; red errors and yellow warnings; filter by error; find and replace, including empty values | Mapping is standard. In-product repair is still rare in accounting software; it mostly lives in dedicated import tools. The comparison also showed me two things the design doesn't do yet: **remembered mappings** for repeat imports (Zoho saves selections; Tally has templates), and a **warning level** separate from errors, so a note like "this category will be created" doesn't read as a failure. ## What the engineer decided, and why credit matters Several of the best behaviours in the built importer weren't in my frames. The senior frontend engineer decided them: - work in the grid is saved as a draft, and an unfinished import can be resumed from **Recent imports**; - a cell is checked when you leave it, not on every keystroke, and its error clears as soon as you change it; - limits are stated at upload, and a column with data but no heading is rejected with a message instead of being dropped silently. A design-only case that takes credit for build decisions isn't honest. It also taught me that a handoff has to carry behaviour, not only looks. Arrow-key movement and pasting a block of values weren't in my frames, and they aren't in the build yet. Next time I'd add a short interaction spec: a keyboard map, and what Enter and Tab do. I write more about handoff in [being the only designer on a product team](/blog/only-designer-on-the-team). ## A checklist for error recovery in import UX - [ ] Accept people's own column names and suggest matches, with sample values. - [ ] Say what is required for *this* account, on the mapping page. - [ ] Block early, on the field, and name what's missing. - [ ] Put every error on its cell, with a visible marker, not colour alone. - [ ] Use typed editors so wrong values can't be typed in the first place. - [ ] Count and filter by error, per column. - [ ] Let one value fix many selected rows, and state the scope first. - [ ] Save progress; let people leave and resume. - [ ] Post whole documents, and confirm anything irreversible. - [ ] Give empty, rejected-file and interrupted states their own wording (see my [state checklist for business software](/blog/state-design-checklist)). ## What shipped, and what isn't measured The design was handed off, marked ready for development, before the build began on 2 June 2026. Delivery Note and Goods Received Note import reached a tagged release in July 2026, and Inventory Adjustment import followed in August, all on the same mapping and repair pages. Product/Service import was on UAT in September. Measured outcome: none. I have no import counts, success rates or time-to-import figures. If I could measure one thing first, it would be whether the round trip is really gone: the same file uploaded again within a day. So the claim is narrow. Supported errors (typed values, references, required cells, repeated fixes) are repaired inside the product before posting. Some problems still start in the file, and how I label claims like this is the subject of [writing honest case studies](/blog/honest-case-studies). ## References - Intuit, [Import products and services into QuickBooks Online](https://quickbooks.intuit.com/learn-support/en-us/help-article/list-management/import-products-services-quickbooks-online/L4o3mXx2u_US_en_US). - Flatfile, [Importing Data with Flatfile: Overview](https://support.flatfile.com/articles/7763163677-importing-data-with-flatfile-overview). - Zoho, [Import Data, Zoho Inventory user guide](https://www.zoho.com/us/inventory/help/import-export/import.html). - Odoo, [Export and import data, Odoo 19.0 documentation](https://www.odoo.com/documentation/19.0/applications/essentials/export_import_data.html). - Tally Solutions, [Import data in TallyPrime](https://help.tallysolutions.com/import-data-in-tally/). - Tim Neusesser and Evan Sunwall, [Error-Message Guidelines](https://www.nngroup.com/articles/error-message-guidelines/), Nielsen Norman Group. All vendor pages were read in September and October 2026. # Blog: Being the only designer on a product team URL: https://www.bibhushansaakha.com.np/blog/only-designer-on-the-team · Published 2026-09-29 Being the only designer on a product team means you own the whole path from a request to a shipped screen, with no design peers to review your work. At Tigg, the accounting and POS product I work on at BIC Technology, that path is: the CEO writes a ticket and explains it, I design in Figma, the CEO and the senior frontend engineer sometimes review, I mark the page "Ready for dev", and the work goes through development, a dev environment, QA, UAT, QA again and a production release. What makes it work as a solo designer is a clear handoff, drawing every state, and treating QA, support and the CEO as research channels. I joined Tigg in April 2025 as a three-month intern and have been the only UI/UX designer since. I'm now a UI/UX and frontend engineer, so on some features I design and build, and on others I design and hand over. This post describes the design process as it actually runs, and what I'd tell someone starting in the same seat. ## What being a solo designer actually looks like Most of my week is with two people: the CEO and a senior frontend engineer. From time to time I work with backend engineers and DevOps, with QA on bug fixes, and sometimes with sales. There is no design manager, no researcher and no second designer to argue with. That shapes the work in three ways: - **Review comes from outside design.** The CEO reviews for product fit, the senior frontend engineer for what can be built well. Neither reviews typography or spacing the way a design peer would, so I have to hold that standard myself. - **Research comes second-hand.** I learn about user problems mostly from QA, support and the CEO. That's real signal, but it's filtered, and I say so in my case studies. - **I'm often the builder too.** On [cash sessions](/work/cash-sessions) I designed the web and mobile screens and built the web frontend. On [bulk import](/work/bulk-import) I designed every state and the senior frontend engineer built all of it. ## Our design process, from ticket to release | Stage | Who is involved | What I do | |---|---|---| | Ticket | CEO writes and explains it | Ask questions until I can restate the problem | | Design | Me, in Figma | Draw the flow and every state | | Review | CEO and senior frontend engineer, sometimes; QA, sales or backend when it touches them | Take feedback, change the design | | Ready for dev | Me | Mark the final page; move explorations to an archive page | | Development | Senior frontend engineer, me, backend, mobile developer | Build my part, or answer questions on the design | | Dev environment | Team | Check the build against the design | | QA | QA | Fix what QA finds in my parts | | UAT | Team | Adjust details found in acceptance testing | | QA again | QA | Re-test after UAT changes | | Production release | Team | Watch for reports through support and the CEO | ### The ticket For new features, the CEO writes the ticket and explains it to me or to the senior frontend engineer. Sometimes the request starts elsewhere: I believe [cash sessions](/work/cash-sessions) started as a sales request that went to the CEO. On the [configuration redesign](/work/configuration), the CEO wrote the brief and later made a six-group prototype himself. My first job is to understand the problem well enough to explain it back. ### Design in Figma I design the flow and the states, not just the main screen. I also look at how other products solve the same problem. Zoho is the product I usually look to for patterns, and for cash sessions I looked at many other POS apps. Some problems are easier to design in the browser than in Figma. For the configuration redesign I built the CEO's grouping as working navigation inside the product, so we could try it with the real settings instead of on a diagram. Building it showed where the grouping strained in a way a diagram wouldn't have. ### Review Review is informal. The CEO and the senior frontend engineer sometimes look at my Figma. When a design touches someone else's work, QA, sales or a backend engineer may be pulled in. The senior frontend engineer is especially useful early: a design that fights the existing components costs everyone time later. ### Ready for dev: the 🟢 and 🔴 pages The handoff is a Figma page marked **Ready for dev**. I keep two pages per feature: - **🟢 page:** the final design. Every state is its own named frame: nothing mapped, some mapped, required fields still unmapped, all clean. - **🔴 page:** an archive of what I tried. I don't delete ideas that lose; I move them there because they might be useful later. [Figure: The bulk import session as a state diagram, from the case study. Names in brackets are frames on the 🟢 page. Each state the build had to handle was drawn as its own frame before development began.] The split matters more than it looks. A developer opening the page should never have to guess which version is final. For [bulk import](/work/bulk-import), both pages were finished before the build began on 2 June 2026, and the senior frontend engineer built from the 🟢 page. ### Development, QA, UAT, QA again After handoff the work goes to development, then to a dev environment where the team can try it, then QA, then UAT, then QA again, and only then to production. When I've built part of it, I fix what QA finds. When I haven't, I answer questions and check the build against the design. QA's follow-up list is often the most concrete feedback I get on a feature. ## How I keep design quality without a design team These are the habits that do the job a design peer would otherwise do. ### Draw every state Empty, zero, loading, partial, denied, expired, interrupted. If a state isn't drawn, someone will improvise it during the build, usually under time pressure. I keep a list, written up in [the state checklist for business software](/blog/state-design-checklist). ### Write the reason next to the decision Decisions get questioned weeks later, by people who weren't there. A one-line reason beside a frame ("the note is required only when the drawer is not balanced") saves a meeting. ### Separate final from exploration The 🟢 and 🔴 pages are the cheapest process improvement I know. They make the handoff unambiguous and keep the history. ### Be honest about the evidence When a design rests on the CEO's knowledge and patterns from other products, not on user testing, I say so. It keeps me careful, and it makes the work easier to trust. I wrote about this in [writing honest case studies](/blog/honest-case-studies). ## Solo designer vs designer on a design team | | Solo designer | Designer on a design team | |---|---|---| | Design review | From product and engineering | From design peers | | Research | Mostly second-hand (QA, support, CEO) | Often a researcher or a research practice | | Consistency | You are the design system | Shared ownership | | Scope | Every feature, every surface | Usually one area | | Building | Often expected | Often not | Neither is better. The solo role gives you range quickly; a team gives you depth and critique. If you're solo, you have to create some of that critique yourself. ## What I'd tell someone starting as the only designer - Learn the domain before the tools. In accounting and POS, a pretty screen that bends a business rule is a bug. - Ask the senior engineer to look early, not at handoff. - Draw the unhappy paths first; the happy path is the one everyone remembers anyway. - Keep an archive of explorations. You'll reuse more of it than you expect. - Learn enough frontend to prototype in the browser. Some decisions only show up when the thing runs. - Treat QA as a design partner. They find the states you didn't draw. None of this is measured. It's how the work runs at Tigg today, and it's what has held up for me so far. # Blog: Designing software for Nepali businesses URL: https://www.bibhushansaakha.com.np/blog/designing-for-nepali-businesses · Published 2026-09-27 Designing software for Nepali businesses means treating local rules as correctness, not polish. Amounts are in NPR and are often grouped in lakhs, dates are in Bikram Sambat, the fiscal year starts on Shrawan 1, a PAN has exactly nine digits, VAT at 13% is often already inside the price, Nepal's clock is UTC+05:45, and a shop's day mixes cash, credit tabs and QR payments. Each of these changes what a form accepts, what it fills in by default and what a summary shows, so getting one wrong produces a screen that looks fine and records the wrong thing. I'm the only UI/UX designer on Tigg, an accounting and POS product for Nepali businesses made by BIC Technology. This post collects what I've learned about UX design for Nepal from that work, with links to the cases where each lesson comes from. ## What changes when you design for Nepal The short version is in this table. The rest of the post explains the rows that taught me the most. | Nepal reality | What it changes in the design | Where I met it | |---|---|---| | NPR is the base currency | Every rate is "to NPR"; NPR is listed first; every amount carries its currency | [Currency case](/work/currency-reconciliation) | | Lakh grouping (1,66,750) | Number formatting can't assume groups of three | [Currency case](/work/currency-reconciliation) | | Bikram Sambat calendar | Date pickers, defaults and conversion to AD | [Admin and ERP case](/work/admin-erp) | | Fiscal year from Shrawan 1 (mid-July) | Default accounting start dates, year-end numbering | [Admin and ERP case](/work/admin-erp) | | Nine-digit PAN | Exact-length validation | [Admin and ERP case](/work/admin-erp) | | 13% VAT, often inclusive | Prices that must not be taxed twice | [Batch and serial case](/work/batch-serial) | | Notes from Rs 1 to Rs 1,000, plus Rs 25 and Rs 250 | An editable denomination list | [Cash sessions case](/work/cash-sessions) | | Credit tabs settled in cash | Its own row in the cash drawer | [Cash sessions case](/work/cash-sessions) | | Cash alongside QR payments | Count only cash in the drawer; show the QR to the customer | [POS operations case](/work/pos-operations) | | UTC+05:45 | Timestamps that are easy to get wrong by 15 minutes | [Cash sessions case](/work/cash-sessions) | | Local words such as "Godown" | Field matching and labels | [Bulk import case](/work/bulk-import) | ## Money: NPR, lakhs and the rate to NPR Most Nepali businesses keep their books only in rupees, so NPR is the base. When multi-currency support arrived, I set three rules for the money forms: an amount always carries its currency; the bank account decides the currency of a payment; and leaving NPR clears the exchange rate. The last rule exists because of a Nepal-shaped bug. NPR's rate to NPR is 1, so a form that opens in NPR shows 1. Switch to USD and the 1 used to stay, so a USD 1,250 payment could be saved as NPR 1,250 instead of roughly NPR 1,66,750. A default that is true in one context became a wrong number in another. The fix was to empty the rate and make it required whenever the form leaves NPR. Number formatting is part of the same problem. Nepali readers group large amounts in lakhs and crores (1,66,750), not only in thousands (166,750). A product should pick one format and use it everywhere, with the currency beside the number. ## Dates: designing a Bikram Sambat date picker Nepali businesses work in Bikram Sambat (BS), alongside the Gregorian calendar (AD). The fiscal year starts on Shrawan 1, which falls in mid-July. That shapes three things in a date picker. ### Show the calendar people use Where the business thinks in BS, the picker should show and accept BS dates, with AD conversion behind it. Conversion errors are not cosmetic: a wrong date moves a document into the wrong fiscal year. ### Never refill a cleared field In Tigg's company-creation flow, the accounting start date uses a BS date picker. If you cleared it and pressed Enter or Tab, it used to fill a default date back in. That is worse than an error, because it looks like your own input. I fixed it so a cleared field closes and stays empty, and the "required" message can appear. I wrote about the general rule in [empty is not zero](/blog/empty-is-not-zero). ### Work out defaults from the calendar The default accounting start date is mid-July because of Shrawan 1. Today that default is a fixed date that someone has to update each year. Working it out from today's BS date is on my list. It's a small example of a wider rule: a local default should come from the local calendar, not from a constant. ## Tax: nine-digit PAN and inclusive VAT Nepal's Inland Revenue Department (IRD) issues a nine-digit PAN. Tigg's company form accepted 9 to 12 digits, and in November 2025 I changed it to exactly nine. A wrong tax number is easy to type and hard to notice later, so exact validation is kinder than a loose one. VAT is 13%, and many Tigg businesses price with VAT included. That matters wherever a price comes from somewhere else. In batch tracking, each batch carries its own selling price, and that price has to reach the order line as the final price without VAT being added a second time. The configuration redesign I'm working on follows the same logic: IRD sync appears only for organisations that have IRD enabled and verified, so people don't see controls that don't apply to them. ## Cash, credit and QR at the counter A Nepali shop's drawer is not just sales. In designing cash sessions I wrote the drawer down as a ledger first, and three local patterns showed up: - **Credit tabs.** Customers often run a tab and pay later. That cash is real but isn't a new sale, so it has its own row. - **Cash beside QR.** Shops take QR and wallet payments next to cash. The drawer card counts only the cash share; other methods stay in the transaction summary. - **Local notes.** The default count goes from Rs 1,000 down to Rs 1, in the order a cashier counts. Rs 25 and Rs 250 are less common, so they aren't in the default, but a shop can add them. [Figure: One fictional session as a waterfall: opening count, cash in and out, cash sales, refunds and credit collected in cash. The movements add up to an expected Rs 17,600; the cashier counts Rs 17,300.] QR payments have their own local shape. In many shops a digital payment starts with a printed QR on a stand, and the customer types the amount. A dynamic QR carries the amount for one bill. I designed the screens for a small customer-facing display, NiziPOS by Yarsa Tech, so the customer sees the amount, the QR and the result without typing anything. ## Time: UTC+05:45 Nepal is five hours and forty-five minutes ahead of UTC. That 45 minutes is a common source of bugs. In cash sessions, timestamps came back with the offset written in more than one form, and the history has to read both, because a quarter-hour slip is enough to make a sequence of corrections look out of order. Session times also moved to a 24-hour clock. ## Shared counters and local words Two smaller lessons: - **Devices are shared; records shouldn't be.** A counter tablet is used by several people across shifts. So a cash session belongs to one person, and only that person or an admin can change its drawer. Others can see it. - **Use the words people use.** Businesses in Nepal often say "Godown" for a warehouse. An importer that only matches "Warehouse" fails on a perfectly normal sheet, so the bulk import's column matching treats them as the same field. ## A checklist for UX design in Nepal - Are amounts shown with their currency, in one consistent grouping? - Does any default (a rate, a date, a tax setting) stop being true when the context changes? - Do date fields use BS where the business does, and stay empty when cleared? - Do defaults follow the fiscal year from Shrawan 1? - Is PAN validated as exactly nine digits? - Are VAT-inclusive prices protected from being taxed twice? - Does the cash flow separate cash from QR and credit collection? - Can shops edit the denomination list? - Are timestamps correct at +05:45? - Do labels and matching accept local words? ## Where this knowledge comes from I should be honest about the method. I learn about user problems mostly through the CEO, QA, support and sales. I haven't run field studies in shops for these features, and none of the outcomes above has been measured. Most of these lessons came from bugs and edge cases, so I now run local rules as a checklist before design review rather than fixing them later. For how that review fits into the team's process, see [being the only designer on a product team](/blog/only-designer-on-the-team). # Blog: Blind counts: designing against anchoring at the cash drawer URL: https://www.bibhushansaakha.com.np/blog/blind-counts-and-anchoring · Published 2026-09-24 A blind count asks the cashier to count the drawer and submit the number **before** the system shows how much cash should be there. I designed it that way for Tigg POS because an expected amount on screen works as an anchor: anchoring research shows that people adjust too little from a number they have just seen, so a visible target invites the cashier to recount until the count agrees, or simply to type the target. Hiding the expected amount until after the count keeps the count independent, at the cost of one extra step and a little less help for the cashier. This post explains how the blind count works in Tigg's cash sessions, the behavioural reasoning behind it, and what it costs. The full design is in [the cash sessions case](/work/cash-sessions). ## What is a blind count? At the end of a shift, the cashier closes the session. Closing has two phases: 1. **Count.** The cashier enters a total, or counts note by note from Rs 1,000 down to Rs 1. Nothing on screen says what the answer "should" be, and there is no note field yet. 2. **Review.** After **Submit Count**, the system fetches the expected cash and shows expected, counted, the difference and a plain-language result: balanced, short or excess. If the drawer is not balanced, a note becomes required before the session can close. [Figure: The two-phase close in Tigg POS. 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.] The decision was deliberate from the start, not something I added after testing. My goal was simple to state: reduce the bias towards matching the expected amount. ## How anchoring affects a cash count Anchoring is one of the three heuristics Tversky and Kahneman described in their 1974 paper in *Science*. In their best-known demonstration, people watched a wheel of fortune produce a number between 0 and 100, said whether a quantity was higher or lower than it, and then estimated the quantity. The arbitrary number moved the answers: the median estimates of the percentage of African countries in the United Nations were 25 and 45 for groups given 10 and 65 as starting points. The paper also reports that payoffs for accuracy did not reduce the effect. Later work suggests this is not only an effect on beginners. In a 1987 study by Northcraft and Neale, both amateurs and professional real estate agents gave property values that moved with the listing price they had been shown. Strack and Mussweiler (1997) explained anchoring as **selective accessibility**: comparing yourself to a number makes information that agrees with it easier to bring to mind. A cash count is not an estimate in the same sense. It is supposed to be a measurement. But a count at the end of a long shift involves small judgements: whether to recount a bundle, whether a stack "looks right", whether a Rs 300 gap is worth another five minutes with a queue waiting. Those are the moments where a target on screen can pull. I didn't test this with cashiers. I designed on the assumption that the research applies closely enough, and I think it's the safer assumption for anything that touches money. A cashier who counts Rs 17,280 and sees Rs 17,600 on screen has every reason to recount until the numbers agree, or to type the target. A count that is made to agree with the expected amount stops being evidence about the drawer. ## Showing the expected amount vs a blind count | Question | Expected amount shown while counting | Blind count | |---|---|---| | Is the count independent? | No: the target is in view | Yes, at the moment of counting | | Can the cashier self-correct? | Yes, by recounting towards the target | Only after submitting, in the review | | What happens to a real shortage? | Easy to absorb into a "recount" or a typed target | Shown as a named difference | | Note when unbalanced | Easy to skip if the numbers are made to agree | Required when short or over | | Steps at close | One | Two: count, then review | | Network dependency | Expected amount loaded with the form | A second request after Submit Count | The blind count is not a new idea; the principle of counting before you see the target is simple. What I had to design was how it feels at a POS counter: what the cashier sees in each phase, and what happens when something goes wrong in between. ## How to design a blind count These are the rules I followed, and the ones I would follow again. ### Remove the target from the moment of counting The count phase shows only what the cashier needs to count: the denominations, a live total of their own count, and Submit Count. No expected cash, no difference, no hint in colour. ### Name the result in words After the count, the review says "Short by Rs 300" in words, not only in red. A small difference is easy to miss in a chart or a colour. In the sample session in the case, Rs 300 is under 2% of the drawer, and it still needs an owner and a reason. ### Ask for a reason only when something is wrong The note is optional when the drawer is balanced and required when it is short or over. A note that is always required soon becomes "ok", typed forty times a week. Asking only when the numbers disagree keeps it rare, so it is more likely to get a real answer. Mussweiler, Strack and Pfeiffer (2000) found that asking people to consider why an anchor might be wrong reduced its effect. The required note is not a debiasing exercise in that sense. But it does the same kind of work after the fact: it asks the cashier to explain the difference, not to make it disappear. ### Design the in-between state Fetching the expected amount is a network request. If it fails, the cashier stays on their count, sees "Couldn't fetch the expected cash. Please try again." and can submit again without losing anything. A blind count that throws away the count on a failed request would teach people to count less carefully. [Figure: The 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.] ## What a blind count costs, and its honest limit The costs are real, and I'd rather name them than hide them. - **An extra step at close.** Two phases instead of one. - **No self-correction against the target.** A cashier who miscounts by a note finds out in the review, not before. - **A second network request** at the moment the cashier wants to go home. - **It can feel like distrust.** That is my concern, not something I have evidence for. The plain-language result and the optional note when balanced are meant to make the close feel routine rather than suspicious. And the limit: during an open session, the Cash Drawer card on the session detail shows the running expected cash, because people need to see the drawer build up through the day. A cashier who wants the target can find it. The two-phase close removes the target from the moment of counting; it does not make the whole product blind. If I changed one thing, it would be an optional blind mode for the whole session, chosen by the owner, because some shops would want it and others wouldn't. ## What I know and don't know The blind count shipped with cash sessions in June 2026. The CEO has reported to me that the feature is live and properly used by real shops. Measured outcome: none. I don't know how often drawers close short, or whether notes on short closes say anything useful. If I could measure one thing, it would be the share of verified sessions that close balanced, short or excess, alongside how many imbalanced closes carry a note longer than a word or two. A blind count that works should produce visible, explained differences, not fewer differences. For the input rules that sit inside the same flow, read [empty is not zero](/blog/empty-is-not-zero). ## References - Tversky, A., & Kahneman, D. (1974). Judgment under uncertainty: Heuristics and biases. *Science*, 185(4157), 1124–1131. [https://doi.org/10.1126/science.185.4157.1124](https://doi.org/10.1126/science.185.4157.1124) - Northcraft, G. B., & Neale, M. A. (1987). Experts, amateurs, and real estate: An anchoring-and-adjustment perspective on property pricing decisions. *Organizational Behavior and Human Decision Processes*, 39(1), 84–97. [https://doi.org/10.1016/0749-5978(87)90046-X](https://doi.org/10.1016/0749-5978(87)90046-X) - Strack, F., & Mussweiler, T. (1997). Explaining the enigmatic anchoring effect: Mechanisms of selective accessibility. *Journal of Personality and Social Psychology*, 73(3), 437–446. [https://doi.org/10.1037/0022-3514.73.3.437](https://doi.org/10.1037/0022-3514.73.3.437) - Mussweiler, T., Strack, F., & Pfeiffer, T. (2000). Overcoming the inevitable anchoring effect: Considering the opposite compensates for selective accessibility. *Personality and Social Psychology Bulletin*, 26(9), 1142–1150. [https://doi.org/10.1177/01461672002611010](https://doi.org/10.1177/01461672002611010) # Blog: Empty is not zero: designing number inputs for money URL: https://www.bibhushansaakha.com.np/blog/empty-is-not-zero · Published 2026-09-20 A blank field and a 0 look almost the same on screen, but in a money input they mean different things: **empty means nobody entered a value, and zero means someone checked and the answer was nothing.** If a form quietly turns blank into 0, a forgotten cash count becomes an honest-looking empty drawer, and every total after it is wrong. Good number input UX keeps the two apart: accept a typed 0 where zero is a real answer, reject a blank, and reject 0 where it describes an event that cannot be zero. I learned to care about this while designing cash sessions for Tigg POS, the point-of-sale product I work on at BIC Technology. The full story is in [the cash sessions case](/work/cash-sessions). This post pulls out the input rules, because they apply to any form that records money, stock or counts. ## Why a blank field is not zero In a cash count, there are two different situations that a careless input treats as one. - **Blank:** the cashier forgot to enter the opening count, or skipped it. The value is *unknown*. - **Zero:** the cashier opened the drawer, looked, and it was empty. The value is *known*, and it is nothing. When I worked out what each one meant for the shop, the answer was simple: empty means they forgot to input; zero means there is no cash. Those lead to different actions. A forgotten count needs someone to go back and count. A zero count is a fact the rest of the session can be checked against. Many form libraries and spreadsheet habits blur this. An empty number field is read as 0 "for convenience", or a sum treats missing cells as 0. In a report that might not matter. In a cash drawer it matters a lot, because the expected cash for the session is built on the opening count: **Expected cash** = opening count + cash sales − cash refunds + credit collected in cash + cash in − cash out If the opening count is silently 0 when it was really Rs 5,000, the drawer will appear Rs 5,000 *over* at close. Nobody did anything wrong at the counter. The interface invented a fact. Blank means unknown. Zero means known and empty. Keeping them apart is a data-integrity decision, not a detail of the input field. ## Balances and events need different rules The second thing I had to work out was that not every money field should treat zero the same way. A **balance** describes how much there is at a moment. An **event** describes something that happened. A balance of zero is normal. An event of zero didn't happen. | Field | What it records | Blank | 0 | Message on reject | |---|---|---|---|---| | Opening count | A balance | Rejected | Accepted, and stays visible | "Enter an opening amount." | | Closing count | A balance | Rejected | Accepted | "Enter a counted amount." | | Cash in | An event | Rejected | Rejected | "Enter an amount greater than zero." | | Cash out | An event | Rejected | Rejected | "Enter an amount greater than zero." | [Figure: Four input states and their outcomes in Tigg POS cash sessions. The status is carried by the words ('Blocked', 'Accepted'), not by colour alone. Fictional data; the rules and messages are the shipped ones.] One case surprised me. When a cashier counts by denomination, with a row for each note from Rs 1,000 down to Rs 1, every row can be at 0. For an opening count that is a valid balance: the drawer is empty. For a cash movement it is not valid, because the sum is zero and a movement of nothing is not a movement. The same input, two different rules, depending on what the field means. ## How to design a number input for money These are the rules I now apply to any field that records money or a count. ### Keep a typed 0 visible If someone types 0, show 0. Don't clear it on blur, don't replace it with a placeholder, and don't format it away. The person needs to see what they confirmed. A field that looks empty after you typed 0 makes people type it again, or doubt that it saved. ### Never turn blank into 0 on save Validation should see the difference between "no value" and "the value zero". If the field is required, a blank should block with a message that says what to do. If the field is optional, blank should be stored as "not entered", not as 0. ### Reject zero only where zero is impossible Ask whether the field is a balance or an event. Balances accept zero. Events, such as a payment, a transfer or a cash movement, need an amount greater than zero. ### Write the message as an instruction "Enter an opening amount." tells the cashier what to do next. "Invalid value" doesn't. Short, specific, and on the field itself. ### Treat defaults as claims A pre-filled number is more dangerous than an empty field, because it passes validation. I learned this on a different feature. In Tigg's multi-currency forms, an exchange rate of 1 is correct for NPR, but if someone switched the currency to USD the 1 stayed, and saving worked. The fix was to clear the rate and make it required whenever the form leaves NPR. The details are in [the currency case](/work/currency-reconciliation). ### Let a cleared field stay cleared The same idea applies to dates. In Tigg's company-creation flow, clearing a Bikram Sambat date and pressing Tab used to fill a default date back in, which looks like the person's own input. The fix was to let a cleared field stay empty so the "required" message could do its job. It is in [the admin and ERP case](/work/admin-erp). ## Empty vs zero: a checklist for form validation Before I call a money form done, I check each field against these questions: - Does the field record a balance or an event? - What does blank mean here: unknown, not applicable, or skipped on purpose? - Is 0 a real answer? If yes, is it accepted and still visible after saving? - Does the save path ever turn blank into 0, or a missing value into a default? - Is any value pre-filled? Is that value true in every context the form can reach? - Does each rejection say what to do, in words, on the field? - When a total is computed, are unknown parts shown as unknown, not as zero? The last question matters most for screens that summarise. In cash sessions, a session that was started without an opening count does not show an opening of Rs 0. At a location that verifies cash, the drawer card says the opening balance still needs to be entered, with a button to enter it. An unknown part of a sum should look unknown. ## What the rule costs Strict input rules cost a little speed. A cashier with a really empty drawer has to type 0 instead of pressing Start with the field blank. I think that is the right trade: one keystroke turns an assumption into a record. It is also why the strict rules apply only at locations that switch on cash verification. Shops that don't want counts can start and close sessions in one click each, and add counts later if they want. I should be clear about the evidence. These rules came from reasoning about what each number means, an internal walkthrough with real cash drawers, and QA's testing. They were not tested with cashiers before release, and no outcome has been measured since. The CEO has reported to me that the feature is live and properly used by real shops. If I could measure one thing here, it would be how often a verified session closes with a count at all. The rule is small enough to carry anywhere: in anything financial, nothing should never have two meanings. For more states like this one, see [the state checklist for business software](/blog/state-design-checklist). For the decision that sits next to it in the closing flow, read [blind counts and anchoring](/blog/blind-counts-and-anchoring).