Back to blog
tech Apr 27, 2026 · 3 min read

Building Spellbook, one 'Oh God!' at a time

Three things I underestimated while building a campaign tool: offline sync, AI context, and rich text editors from scratch.

Cover image

Most engineering blog posts focus on the success story. I’d rather share my recent “Oh God!” moments while writing software — the points where you try adding a specific feature and realize it was harder than you thought.

Long story short: I love tabletop RPGs. I started playing when I was 16 and I never stopped. I currently run an almost-weekly campaign with some friends. I used Obsidian and Notion a lot, but I needed a single tool to manage, write, share and create all the files I need to run a campaign. For the people who don’t know what I’m talking about, picture it like a budget “Cursor” for generic writing, with sharing features.

So I (kinda) coded it! It sits somewhere between a proof of concept and a product, but I don’t really care, it’s something I need first and foremost. It’s a Flutter frontend, since I wanted to have the app on both my laptop and smartphone, and a NestJS backend, since I already use it for work and I like it. I’m still not the biggest Flutter fan, I find it really hard to follow the cascade of nested widgets. For a proof of concept, it probably would have been better to just use a web framework and wrap it with Capacitor.

Now, to the “Oh God!” part:

Oh God! Offline sync is hard.

I don’t really regret starting with an offline-first mindset — doing it later would have required quite a bit of refactoring — but handling conflicts, failed syncs, push and pull logic, cleanups and many other things took some time. And honestly, what I have now is a basic versioning system that only covers a fraction of the edge cases.

Oh God! Telling the AI what to do is hard.

Naive of me to think that context was easy to manage. When you start factoring in cost and performance, you have to do a lot of work to find a sweet spot. I don’t want the AI to read all 400 pages the user has written just to produce a faulty output bloated with noisy data. If I tell the AI “create a character that lives in the Edenmoor village and is a great friend of Merra,” I want it to really know what the user means. Between folder schemas, RAGs and agentic loops, I think I found a nice solution that should scale well in terms of cost.

Oh God! Creating a good text editor from scratch is hard.

I’m currently using a library, but I’m migrating away from it because of web performance issues and licensing. I’m using AI extensively for both researching and implementing this Notion-like rich text editor. It wasn’t the first time I tried to write a block text editor from scratch — I already knew it wasn’t the simplest task. Still, it gets hard when you start factoring in all the different devices, virtual keyboards, IME, etc.


And that’s all for today. Building something for yourself is the best way to discover how many problems you didn’t know you had, and a great reminder that “simple features” almost never are. Hopefully I’ll share some other “Oh God!” moments in the future.