Inspiration

I manage cost and supply chain for apparel companies. Factory negotiations, material costs, delivery dates — companies have entire systems and teams for this work.

Then I watched someone sell a handmade earring for ¥1,800 at a craft fair. She knew what the resin and findings cost. She did not know the marketplace fee. She had absorbed the shipping. She had never once counted her own 45 minutes of work.

I ran her numbers: ¥1,800 in, ¥1,420 of materials, shipping and her own labour at ¥1,000/hour, ¥197 to the marketplace. ¥183 left.

She had no idea. Not because she is bad at maths — because nothing she owned was going to tell her.

What it does

nuime is a cost and profit tracker for independent makers, built for one hand at a fabric shop counter.

Products and materials — Add a product, attach the materials you used, and the cost adds itself up. Materials are grouped into fabric, findings, labour, shipping and other. Photos of receipts can be attached for reference.

Your own labour is a cost line — There is no timer. You decide your own hourly rate, enter it as a labour cost next to fabric and shipping, and the number you see already includes paying yourself.

What you actually keep — Enter a selling price and nuime shows the gross margin and the amount left after the marketplace fee. Fee rates for each sales channel are editable, so the number matches wherever you actually sell.

Sales are frozen when recorded — Mark a product sold and the price, cost and fee are stored with that sale. Editing the product later never rewrites a past result — the same principle as bookkeeping.

Monthly dashboard — Revenue, spending and the breakdown by category, month by month.

Deadlines with reminders — Give a custom order a due date. A local notification arrives three days before and again the day before.

Everything stays on the phone — No account, no server, no data collection. It works offline.

How we built it

SwiftUI and SwiftData, iOS 17+, iPhone only. RevenueCat for purchases. Built solo over five milestones between June and September 2026.

RevenueCat handles product fetch, purchase and entitlement checking. The paywall itself is written in the app, not from a template — three reasons:

  1. A non-consumable one-time purchase sits alongside two subscriptions, and I wanted to control exactly how the three are presented together.
  2. The wording on that screen is part of what the product argues. It belongs in the codebase, under review, next to the tests.
  3. In an app about money, I am not willing to let an external dashboard decide what price appears on screen.

Prices come from localizedPriceString, never hardcoded. The per-month equivalent for the annual plan uses localizedPricePerMonth rather than my own division. Trial availability is read from the product's introductoryDiscount, not decided in app code — if the store configuration changes, the screen changes with it instead of lying.

The three plans are identified by packageType, never by product ID. Product IDs contain prices (monthly_480) and IDs cannot be changed later.

1,642 tests across two devices. Every test suite that matters has a negative control: I deliberately break the implementation and confirm the test goes red. A test that cannot fail is not protecting anything.

Challenges we ran into

The test that was green for the wrong reason. While building the free tier limit, all 19 tests passed while CSV export was quietly available to free users. The tests checked that the feature worked. None checked that it was withheld. The line between free and paid is an invariant, and nothing was guarding it.

I now break every new test on purpose before trusting it — and I break it the way a developer would actually get it wrong. For the trial-badge logic, the obvious break (return true always) was caught immediately. The one that mattered was a plausible mistake: "show 7 days free unless it's the non-consumable." That reads correctly, passes review, and only lies the day Apple's configuration changes.

The tests that made a change impossible. Japan's largest handmade marketplace changed its fee rate. I went to update the default. Twenty-three test files had the old rate's arithmetic baked in as constants. Changing one number meant rewriting three hundred expectations by hand, eleven days before submission, with no safe way to verify I hadn't broken one.

I shipped the old rate and filed the real fix: teach the tests to derive expectations from the fee store instead of hardcoding them. The lesson is narrower than "don't use constants" — constants are correct for verifying calculation logic. They are wrong for anything that depends on a configuration value, because they silently make that value unchangeable.

Four things have to be true before a product exists. A purchase wouldn't load. Logs said the request was sent. The request being sent and the product being returned are different events — two of three came back, one didn't, and the warning was printed after the success lines.

The cause was a missing localization. Availability, price and localization all have to be set, and an unset field produces no error at all. It simply isn't there.

The arithmetic that didn't close. The raw fee on a ¥1,800 sale is ¥196.02. Print ¥196 and the column on screen is off by one yen. I display the fee as the residual instead, so price minus cost minus fee always equals what's left. In an app about money, a column that doesn't add up costs more trust than a rounding error costs accuracy.

Accomplishments that we're proud of

The arithmetic on screen always closes. Every screen that shows a subtraction adds up to the yen.

Nothing that has happened can change. A recorded sale keeps its price, cost and fee forever. Edit the product afterwards and last month's total stays exactly where it was.

The app refuses to grow settings. No hourly-rate timer, no inventory counts, no configurable free tier. Every feature request gets the same first question: what does this stop the maker from having to hold in her head? If the answer isn't immediate, it doesn't ship.

It does not break when you need it larger. The donut chart is built from its width, never a fixed height. At accessibility text sizes the centre total is removed rather than shrunk — a number too small to read is worse than no number.

Shipped v1.0 on August 31. Shipped paid on September 23. Solo, alongside a full-time job.

What we learned

A test suite is not a safety net by default. It becomes one only when you have watched each test fail for the right reason.

Verification documents are themselves unverified. My own checklist said a button should become unpressable after a restore. The implementation was correct — restores are idempotent, so pressing again is the right behaviour — and my document would have had me report working code as a bug.

Corrections need their conditions attached. "192 is wrong" was true only while the app shipped one fee rate. Written without that condition, it would one day be used to reject the correct value.

Fixing a number in four places means it will be wrong in one of them. Every figure that appears in code, tests, store images, a demo video and a description has five places to drift apart. The answer is not discipline. The answer is fewer places.

What's next for nuime

Near term: aligning the marketplace fee defaults with the latest published rates, once the tests can derive their expectations instead of hardcoding them. That refactor comes first — shipping the number without it would leave the same wall standing.

The monthly dashboard currently reports revenue and spending without deducting marketplace fees, while the per-product view does deduct them. Both are defensible in accounting terms and both are intentional, but the app should not present two different arithmetic in two places. Deciding which one is right, and migrating carefully, is the next substantial piece of work.

Beyond that: English localization of the interface, and sales channel presets for makers outside Japan. Both wait until the Japanese version earns its keep.

Awards we're entering

nuime is entered for three awards.

HAMM (Hackathon's Actual Monetization Metric) — three purchase paths shipped and verified on device: monthly, yearly, and a one-time purchase, with a 7-day trial on both subscriptions. Enrolled in Apple's Small Business Program, so the arithmetic behind the target is real take-home, not gross.

Peace Prize — the users are one-person handmade businesses, overwhelmingly women, systematically underpaid because the arithmetic is scattered across four places and nobody counts their hours. The app puts the number in front of them while the price is still being set.

Design Award — nuime has no custom animations, and that is the point. The arithmetic on screen always closes. Recorded sales never change. The app refuses to grow settings. The craft is in what it will not do.

The details for each are in the category answer fields.

Built With

Share this project:

Updates

Submission history