All articles

How the budget works in Unitcore

The budget is the first Unitcore product, and it follows the same rule as the rest: everything lives inside a space and everyone in that space sees it. In your personal space that means your own spending; in a family space it is shared, so nobody has to send screenshots around to work out where the month went.

What follows goes from the simple to the deep end: what a record of money is made of, what the dashboard shows, what the assistant can do, and how it all works underneath.

What a record is made of

A transaction is one trip to the shop, not one number. It holds items, and the category sits on each item rather than on the whole thing.

Supermarket4 September · EUR
Groceries for the weekFood38.10
Laundry detergentHouse6.20
Carrier bagUncategorized3.00
Total47.30

One receipt, three lines, three different categories. With the category on the transaction instead, “Food” and “House” would have to be recorded as two separate trips to the same shop.

Categories belong to the space and come in two kinds: expense and income. A category has nothing else to it — no limit, no colour, no nesting.

Amounts are always positive

There is not a single negative number in the database: an amount is always “how much”, and the sign comes from the category. Salary against an income category arrives as a plus, groceries against an expense category as a minus, and the database works that out rather than the app.

Money with no category counts as spending. That is a decision, not a forgotten branch: an uncategorized line is usually a purchase nobody has filed yet, and it should spoil the month's total rather than quietly vanish from it.

It also follows that deleting a category never deletes money: the items stay exactly where they are and simply lose their label. You can rebuild your categories whenever you like and your history survives it.

What the dashboard shows

One card per currency. Currencies are never added together — there is no exchange rate anywhere in this project, and a single “total spent” figure would be invented.

Spent in September
1,240.00EUR
−12% against last month
Food430.00
House260.00
Uncategorized95.00
Spent in September
820.00GBP
+4% against last month
Phone280.00
Other170.00

Each card answers the same four questions about its own currency: how much this month, how that compares with last month, what the past six months looked like, and what the money went on. Five categories are named; everything past that folds into “Other”.

Underneath sits the recent activity from all of your spaces at once. The dashboard shows them together by default, and the filter at the top narrows it to a single space.

Writing something down by hand

The form opens straight from a card: a name, a date, a currency, and one line per purchase. A category can be created without leaving the form, along with the choice of expense or income. Open a transaction that already exists and the same form corrects or deletes it.

Deleting a transaction cannot be undone. There is no trash here the way there is for tasks: the transaction goes, its items go with it, and nothing comes back. It is the only action in the budget you cannot take back.

The same thing, said out loud

Eleven tools
Three that read, eight that write. The assistant works in whichever space you are in.
in the evening
“Log the supermarket: groceries 38.10, detergent 6.20” → one transaction, two items.
end of the month
“How much did we spend on food in July?” → totals per category for the period.
after a slip
“Change that to 41.30” → the item is corrected, the transaction stays as it is.
in a shared space
“What has house cost us since spring?” → transactions with their items.

Two decisions worth saying out loud:

  • Write access is granted per connection. An unfamiliar client that reaches your account can only read; it cannot write anything until you allow that connection to, in your settings.
  • A transaction never survives without its items. If the items fail to save, the assistant deletes the transaction it just made: an empty record in your books is worse than no record at all.

How it works underneath

A schema of its own in the database, and no application layer in between: the web app and the assistant both talk to the same database as you, and the database itself decides what you are allowed to see.

Clients

Read from a prepared view, write straight to the tables.

dashboardtransaction form
Assistant

The same queries as the same user — only called with words.

summarylist_transactionscreate_transaction…8 more
Permissions

One check across every table, a list of columns nothing outside the database may write, and a separate gate for connected assistants.

RLS: a member of this space?column-level grantsper-client write access
Data

Three tables and the triggers that hold the rules: items inherit the date and currency of their transaction, and an amount is always above zero.

categoriestransactionstransaction_items
Reading

The view fills in the category name and the sign of the amount, so no client has to work that out — or work it out differently.

transaction_items_signed
One access boundary, in the database rather than in three clients
The assistant physically cannot see more than you do
Sign and category are decided in one place, so the numbers agree everywhere
A new client plugs in without a new backend
The SQL details, for anyone curious
  • An item references its transaction and its category as a pair, (id, space_id). A category from another space is therefore structurally impossible — no check in application code required.
  • Synchronisation only ever runs downwards: the date and the currency live on the transaction and the items inherit them. Move a receipt's date and every line moves with it; the reverse cannot happen, because those columns are not granted on an item at all.
  • amount numeric(14,4). Three decimal places are needed in practice — the dinar and the rial have a thousandth as their minor unit; the fourth is kept for the remainders of splitting a bill between people and for conversion, so the lines still add up to the receipt.
  • The currency is a domain checked against ^[A-Z]{3}$ rather than a fixed list: the product does not decide what you count in.
  • “Who wrote it down” is stamped from your session and is not granted to any client.
  • The dashboard reads half a year a page at a time: PostgREST returns at most 1,000 rows per request, whatever limit the code asks for.

What is deliberately missing

No exchange rates or conversion, no recurring payments, no accounts and balances, no per-category limits, no bank statement imports, and no splitting a receipt between people. That last one is designed — the precision it needs is already reserved in the schema.

The second product on top of the same spaces is tasks, built to the same rules: How tasks work in Unitcore. And if the budget is not connected to an assistant yet, that takes a couple of minutes: Connect Unitcore to Claude.