Building LocalCents: A Local-first Expense Tracker with Automerge CRDTs
Posted on
I previously blogged about What is Local-first Software? back in February of 2025. While my interest has been steady, I never had a real opportunity to get my hands dirty with the technology. Over the past few months, I’ve been tinkering on an educational side project, a simple expense tracker called LocalCents, and have some interesting things to share.
Local-first Software
I won’t rehash the entirety of my previous blog post, but to put it succinctly, local-first software is all about empowering the user. It values:
- local data, and thus quick responses to user commands
- using multiple devices to accomplish your work, and the deterministic syncing tech that makes it work (even during long offline separation)
- seamless collaboration with others without a centralized server
- long-term usage through true data ownership
Many CRDT (conflict-free replicated data type) tools work toward these ideals, but the one I follow most closely is called Automerge.
Automerge comes from the same folks at Ink & Switch who wrote the first essay that coined the term local-first. Automerge is a CRDT persistence layer written in Rust. It compiles to WASM and has native support for JavaScript as well as other languages and frameworks.
LocalCents, a local-first expense tracker
My project LocalCents is an Elixir / Phoenix application (my core competency). I bundle it into a macOS application binary using Tauri (a Rust-based app wrapper library and ecosystem) and use ElixirKit to send messages back and forth between Elixir and Tauri/Rust. To get Automerge working inside my Elixir binary, I use Rustler.
Here are some screenshots of LocalCents in action on macOS:
At face value, not a ton of interesting things are happening here. The repo contains a ./scripts/two-peer-demo.sh script that sets up a more interesting scenario.
The long-term idea is that LocalCents would run on multiple devices or even a website. This script simulates this by running two instances of the app: one as a macOS application binary and another as a web server accessible via a browser.
As you make changes on one device, those changes will broadcast over WebSocket to other connected peers, who apply them immediately. To help demonstrate collisions, the macOS app has a Developer > Enable Offline Mode toggle. With the macOS app offline, you can then make changes that will collide, like changing coffee to $3 on macOS and $5 on the web.
Once you bring the macOS app back online, the changes will be exchanged and a winner chosen.
Aside: This is a key design aspect of CRDTs. When a conflict happens, a deterministic winner must be chosen. In the case of Automerge, the winner is determined through an operation_id (counter, actor) which is a kind of Lamport clock.
One of the areas I was most interested in while working on LocalCents was whether and how I might surface these conflicts to users. There are lots of high-risk domain changes where, if there were conflicting edits across devices or users, I would want some audit trail. For LocalCents, this takes the form of a notification bell warning.
Upon interacting with the bell, you are sent to a new Conflicts tab of the edit pane where you can observe the collision, the value chosen, and the value dismissed. If you prefer the dismissed value, you can choose it to remain as the real value.
Another collision can come in the form of outright deletion. For Automerge, the delete always takes precedence, but again, I offer a warning to a user who has edited an entity that was deleted by another while offline. They can choose to revive the deleted entity or accept its removal.
Final Thoughts and Observations
LocalCents was dreamed up as an educational side project to tinker with Automerge, and it has come to a close. Some final observations:
- Automerge is really cool tech, and since it is built using Rust / WASM, it can be integrated into a variety of environments. The problem space we chose was pretty basic, and there are some concerns, such as access controls, that can be harder to build into a distributed tool like Automerge. Ink & Switch are working on this through Keyhive, though it feels a little bleeding edge.
- Tauri seems equally helpful if you need to do something cross-platform. My biggest red flag would be its iOS support, which still relies on CocoaPods (which is deprecated). I want to see them transition to newer Swift library toolkits. Also, if you are looking to build as close to a native-feeling app as possible, you’d probably use the many Tauri tools to implement features like system-level context menus (in my project I faked them with CSS).
- While I did not go into detail, I used a full GenServer architecture to manage the Automerge document binary. So much of my day-to-day is simple CRUD through a context powered by PostgreSQL/Ecto. It was fun to have built something more BEAMy.
- This project used ElixirKit to talk back and forth between the BEAM and Rust, but there are many others you can look to for inspiration.
- This was also the first time I really truly embraced Phoenix Storybook and managing an isolated component system (internally called
Bond). It worked, but I recommend extracting components rather than building them out in advance, especially early in the project. I found I built things I did not use and then had to remove them. - I used AI a ton in this project. It was a good learning experience (and fueled many other blog posts), but it did take away my true understanding of how Automerge and Rust work. I knew enough to get the demo to work but fear the loose ends I don’t see. For comparison, when I see non-Elixir people using AI on Elixir projects I contribute to, tons of Elixir norms are broken. How many am I breaking in my work here for Automerge and Rust?
What’s Next?
LocalCents was intentionally sketched out as a short-term educational side project. I paused some of my OpenTelemetry research to focus on a single side project, and so I may rekindle that in the near term. I’m also starting to dream about a proper product again. I could imagine a future where I lean on Automerge for such a thing, and it could be in the accounting space, which I frequent in my own work as well as in client work. To support that, I’m reading an early draft of The Mom Test (second edition), which is a great book on customer research.
If you have any questions about the project, let me know, and check out the repo on GitHub.
You might also like
About the Author. Mike Zornek is a developer and teacher focusing on product design and development with a heavy focus on Elixir and LiveView. In between his projects, Mike helps other teams through consulting. During off hours, he enjoyed watching Phillies baseball and playing relaxing video games.
Hopefully, you found interest in my scribbles. If you have commentary or a response, I'd love to hear it.