Shared households & trips
Kitty
Eleven expenses, four people, two transfers.
Shared expenses for a group, with settlement that reduces the tangle to the fewest payments that clear it. Money is integer cents throughout, the arithmetic is checked rather than trusted, and somebody can settle up from a link without making an account.
- Next.js 16
- TypeScript
- Supabase
- PostgreSQL
- Row-level security
- Private file storage
- Zod
- Magic-link sign-in
- Settling up
- Fewest transfers
- Money
- Integer cents
- The hold-out
- No account
reduced from the full tangle
two invariants asserted, never floats
settles from a link
What was going wrong
A group trip's costs end up in a chat thread, a note on somebody's phone and three different memories. The person who paid for the villa is out four hundred pounds and does not want to be the one to bring it up, and the settling-up conversation is six payments long because nobody worked out that most of them cancel.
What we built
Make the maths the product and make it visibly correct. Every amount is integer cents from the moment it is typed. Balances are computed and checked, then reduced to the minimum set of transfers, and both the tangle and the reduction are shown — seeing four people collapse to two payments is the moment the thing lands. Anyone can be paid out or settle up from a link, because the hold-out in every group is the reason these apps get abandoned.
What it looks like
Every screen shown on a desktop and on a phone, because half of these are used standing up.
A group, settled
Balances, then the reduction beneath them. The point is watching the second list be shorter than everyone expected.
/groups/[id]
Desktop capture
1440 × 900
Mobile capture
390 × 844
Adding an expense
Who paid, who it is for, how it splits, and the receipt. The decimal becomes cents once, at this boundary.
/groups/[id]/add
Desktop capture
1440 × 900
Mobile capture
390 × 844
The settle link
What one person owes, what it is made of, and a button. No sign-in, because the hold-out is the whole problem.
/j/[slug]
Desktop capture
1440 × 900
Mobile capture
390 × 844
History and duplicates
Every edit kept as a revision, and the likely duplicate presented as a question with both entries intact.
/groups/[id]/history
Desktop capture
1440 × 900
Mobile capture
390 × 844
What it does
Split any way
Equally, by share, by exact amount or by percentage, with the remainder assigned somewhere explicit.
The tangle and the reduction
See who owes whom, then watch it collapse into the two transfers that clear it. Both views, not just the answer.
Settle from a link
The one person who will never install anything can still see what they owe and mark it paid.
Duplicate detection
Two people adding the same taxi is caught and put as a question, with both entries kept.
Multiple currencies
Converted at the rate stored on the expense, so a later FX move cannot rewrite the trip.
Receipts
Attached and private — half of them have a card's last four digits on them, so they are behind signed URLs only.
Who reaches for it, and when
Four people back from Lisbon
Eleven expenses, every share exactly equal, and two payments instead of the six everyone assumed.
A shared house on the 1st
Standing costs split, and one person who paid the internet reimbursed without a conversation.
The friend who owes £14
Opens a link, sees the figure and what it is made of, and marks it paid. No sign-up, no app.
What makes this one different
The arithmetic is asserted, not assumed
Splits must sum exactly to their expense and a group's balances must sum to exactly zero. Both are checked at runtime, and the screen shows an error rather than a wrong number. A rounding bug that loses a penny destroys trust in the whole thing.
The remainder goes somewhere you can point at
Three people splitting a tenner is 333 / 333 / 334, and the app can say which. Nothing is silently dropped, and no money is ever a floating-point number.
The settlement freezes when it is proposed
Once people are paying, the list must not move because a late expense appeared. It is a document from a moment, not a live recalculation.
It records payments; it does not move money
Mark-as-paid only, and the copy says so. A button that looks like it moves money and does not is worse than no button. Duplicates are a question for the same reason — the app cannot know which entry is real, so it never merges one.
Built with
What each piece is doing here — the people who read this section are usually the ones who would have to maintain it after us.
Next.js 16
The application framework. Pages render on the server by default.
TypeScript
Strict mode everywhere. A field renamed in one place fails to compile in the others.
Supabase
Postgres, authentication, file storage and realtime in one managed service.
PostgreSQL
The database. Constraints and permissions live here, not in the interface.
Row-level security
Who can read which row is decided by Postgres. A UI bug cannot leak data.
Private file storage
Photos and receipts behind signed URLs, namespaced so policies can check them.
Zod
Every input validated on the server, whatever the browser sent.
Magic-link sign-in
An emailed link instead of a password nobody can remember.
Want something like Kitty?
It does not have to be your industry — the underlying problem is nearly always the same. Tell us how the work moves through your business and we will tell you what it would take.
Every project is quoted on its own. Turnaround starts at two weeks.