Yesterday I published 0.0.104 of Actual. This is a big release that fixes one of the biggest problems with the previous implementation, and opens up a lot of possibilities. You can skim the release notes for a quick version.

As mentioned in the release notes, I’m really sorry for being quiet and this release taking so long. I hope you’ll see why this one took a while, and will make it easier to move quickly in the future.

This version includes a completely rewritten budgeting backend. The budget works exactly the same way as before and your data should be transparently migrated — all you will see is that performance is drastically better.

Let me give a little historical context. I started Actual at a time when I was heavily using spreadsheets (about 3-4 years ago), and I was interested in building a system that managed transactions and accounts in a more friendly way, but still allowed you to build your own budgets and reports in a flexible interface inspired by spreadsheets and journal-style systems.

I created a system that managed transactions in SQLite database, and wrote a custom language that could query this data. This language included a parser, compiler, and virtual machine. I figured the language was simple enough (all of that code together was under 1200 lines of code) that it wouldn’t be too distracting, and would provide a lot of power and flexibility.

For example, to create a budget you could query the amount you’ve spent with the following expression:

Copy 1 =from transactions 2 where category = 'food' and 3 date >= '2019-10-01' and date <= '2019-10-31' 4 calculate { sum(amount) })

By adjusting the dates and categories, you could build up a full budgeting system. Expressions could depend on each other, so a category’s balance expression would be someting like =budget_food - spent_food , where spent_food is set to the expression above.

The interesting thing about all of this is that the language is reactive: it tracks which data it depends on and will automatically update whenever it changes. If you categorized a transaction to the “food” category, the above query would automatically run.

Up until 0.0.104, every single number you see in the UI was powered by this. The account balances in the sidebar, the reports, everything. It worked pretty well, but I hit a couple problems.

First, let’s take a step back and talk about customer’s needs. A while ago I realized that I wasn’t building what I originally thought anymore. I’ve learned a lot about listening to customers, looking for real problems, and building things people will actually use. My original idea was a whimsical, cool idea, but that’s about it. Nobody is going to sit down and build their own finance app from scratch with my language.

Over time I improved the budgeting page in Actual more and more and realized I really didn’t need a custom language. I needed to present users with features that already worked and had an intuitive UI. In fact, only presenting low-level primitives is aggressively hostile to users. It was a naive developer’s point of view (that technique works well for code).

Since the language became an implementation detail, I could ask myself if it was working out at least for me. A couple months ago I finally faced reality that it was over-engineered and causing huge problems, and I came up with a much simpler solution.

The biggest problem with the language was performance. You see, whenever you change a budget it has an annoying tendency to propagate changes everywhere. For example, if you have 5 years worth of budgets, and you delete a category, you want to give the user the chance of “merging” the category into an existing one to avoid losing the budgeted amount. Making this change involves going through all of history and changing the budgets in a lot of places.

Unfortunately, changing the budget previously meant changing the custom code for each expression. Doing that many times over adds up and becomes a big problem, especially on mobile. When I introduced syncing across devices, for reasons I won’t explain it aggravated the problem even more. Needlessly to say, this performance problem has hamstrung my ability to implement new features.

I won’t talk about it anymore, just watch this video. The worst case is deleting a category group and merging all of that data in to a new category since it has to do a ton of work to update everything This is deleting 9 categories and merging them into the “Travel” category:

How embarrassing! In the old version it took about 7 seconds. 🤦‍♂️ Did you even see the change in the new version though? It’s essentially instant. The UI immediately updates when the user presses “Delete” and it only takes a tiny amount of time for the “Transfer” category to update.

A big part of Actual’s UX is optimistic updating. That means when you perform an action, don’t wait on the server to finish, just go ahead and update the UI. Most of the UI is implemented with this (transactions, payees, etc) but the budget page wasn’t. Now, not only is the backend far faster, but the budget page always instantly changes whenever you perform an action.

API

This release includes some other big features as well. Actual now provides an API for you to fully query your data. In the video below, the script is getting the budget values for the month of October.

Currently the primary use case of this is to either bulk import all of your data from another app, or write custom transaction importers. Read the docs for more information.

Better sync setup

Another big improvement is how syncing is setup. Previously you could only setup mobile devices to a single desktop. Now you can setup as many desktop devices as you like as well.

It uses multicast DNS to discover devices on the network. No longer do you have to use your phones camera to read a QR code; devices will automatically detect each other if they are broadcasting. Read the docs to learn more.

In the future

This release unlocks a lot of potential for Actual and I’m excited to finally start working on some more big changes. A lot of bugs were fixed as well (check out the release notes).

I learned a lot thinking through my original idea of allowing customizable finance systems. I still think we need something that allows the user to learn and grow. Most finance apps dictate everything, like how the user budgets and reads reports. I’ve learned that most users do want something that works without much work, but I think there’s a balance to be struck between ease of onboarding and giving the user space to learn.

A first obvious step is custom reports: allow the user, when they are ready, to build reports that gives them answers they are looking for. While cash flow and net worth reports solves the most fundamental questions, there’s a lot more you can learn if you use the right tools.