About a month ago I started building a new application called Skillful).
Originally, the idea was quite simple: I wanted an excuse to try Electrobun.
For those unfamiliar with it, Electrobun is essentially an Electron-like desktop runtime built around Bun instead of Chromium + Node.js. The biggest selling point is obvious immediately: no embedded Chromium browser. Smaller runtime, potentially lower memory usage, less “Electron bloat”.
And honestly? That attracted me.
People love complaining about Electron applications shipping half a browser with every app. Personally, I’ve always thought that criticism was slightly overblown. RAM exists to be used, and modern desktop applications are expected to behave like modern applications. Still, the idea of a lightweight filesystem-first desktop app built on Bun sounded genuinely interesting.
So I gave it a shot.
At first, it actually went pretty well.
Then I tried to ship it.
One thing became painfully clear very quickly:
Building a desktop application is not the hard part anymore. Shipping one is.
Packaging, updating, shortcuts, desktop integration, installers, Linux distributions, release channels, runtime quirks, auto-updates, platform conventions… that is the real product surface of a desktop application.
And this is where Electrobun started falling apart.
Not because it was fundamentally bad. In fact, the runtime itself was perfectly fine most of the time.
The problem was that I kept ending up in runtime land instead of product land.
Issues looked like product bugs but turned out to be packaging problems. Or updater problems. Or launcher problems. Or Linux runtime problems.
Every single one costs time, every single one is annoying.
Before I explain why I moved away from it, it’s important to explain why I chose it in the first place.
Skillful is intentionally filesystem-first.
The application manages local AI skills, local models, local assets and local workflows. The disk is effectively the source of truth. I wanted an actual desktop application that happened to have a modern UI, normally I’d use Electron but I wanted to try Electrobun. (hell, the whole idea of Skillful was born because I wanted to use Electrobun)
Electrobun felt ideologically aligned with that.
And architecturally, it fit extremely well. And it also delivered on speed, the filesystem was fast without me applying any efficiency standards. It truly felt well.
One of the best decisions I made early on was separating the runtime shell from the actual application logic.
The project structure looked roughly like this:
src/
main/ -> business logic
shared/ -> shared RPC + contracts
mainview/ -> renderer/frontend
desktop/ -> runtime abstraction layerThe important part here is that src/main/ never cared about the runtime.
All of that was runtime-agnostic. The runtime only needed to satisfy a few interfaces:
That architectural seam ended up saving the project later. At the time though, it just felt like good engineering hygiene.
The first warning signs appeared once I started packaging builds. Every platform had sharp edges.
Windows especially became a rabbit hole of custom glue code and weird packaging hacks.
I ended up maintaining:
bin/bunAnd the frustrating part is:
none of this had anything to do with Skillful itself.
I wasn’t building features anymore. I wasn’t building infrastructure around the runtime.
The Linux packaging story also became especially painful because there wasn’t really a conventional ecosystem around Electrobun releases yet, worse, it doesn’t align with other Linux packaging guidelines.
Electron, meanwhile, has had years of people solving these exact problems already. That difference matters far more than people think.
Eventually I switched the project back to Electron.
Not because Electron was elegant. Not because Electron was lightweight. But because Electron made the difficult parts boring again.
And boring infrastructure is one of the best things you can have.
Using electron-builder, a single configuration now produces:
.dmg and .zip.deb, .rpm, .AppImage, .pacman, .snap, .flatpakThat is an absurd amount of packaging complexity solved by one ecosystem. Previously the Linux story was basically:
“here’s a tarball, good luck.”
Now releases are conventional.
Predictable.
Expected.
That matters.
Another thing Electron completely solved was native desktop integration. Things I spent days wrestling with before became one-liners:
dialog.showOpenDialog()
shell.showItemInFolder(path)
shell.openExternal(url)Icons?
Handled.
Start menu entries?
Handled.
Desktop files?
Handled.
Uninstallers?
Handled. (and actually used.. Electrobun produced an Uninstall.reg but never applied it)
I cannot overstate how valuable it is when platform integration stops being your problem.
Ironically, one of the most interesting bugs happened after migrating. Electron prepends this wonderful little string to IPC errors:
Error invoking remote method 'rpc:call':Which sounds harmless.
Except the domain-specific error transport depended on a sentinel existing at the start of error.message.
So suddenly the custom AppError rehydration stopped working.
The fix itself was hilariously small:
startsWith(...)became:
indexOf(...) >= 0One-line fix.
But the important part was the lesson:
runtime-specific bugs absolutely exist, and the only reason I caught this one was because I ran tests against the actual packaged runtime.
Not just unit tests.
Real packaged application tests.
Without that, I probably would have shipped it without the fix first.
The best thing to come out of the Electrobun experiment was honestly the runtime abstraction layer.
Switching runtimes ended up taking about a single evening.
Not because Electron migration is inherently easy, but because the architecture isolated the runtime shell from the actual application.
The domain layer stayed untouched.
The renderer stayed mostly untouched.
The filesystem model stayed untouched.
The only things rewritten were:
That is exactly what you want from architecture. The runtime became an implementation detail. Which, honestly, it should have been from day one.
Looking back, I think the biggest lesson here is this:
Runtime minimalism is a false economy if you end up rebuilding the ecosystem yourself.
Saving some memory usage on paper is meaningless if your release pipeline needs babysitting every week. Electron absolutely uses more memory. Nobody serious denies that. But the application now:
That trade-off was worth it, instantly. And funnily enough, the filesystem-first architecture survived the migration almost untouched.
Because filesystem-first was never really about Electrobun.
It was an architectural decision. Not a runtime one.
If I had to do it all again?
I’d probably start on Electron immediately. Not because Electron is technologically superior. But because desktop application development is overwhelmingly operational work once the product itself exists.
The runtime you choose should make packaging, updates and desktop integration boring.
Electron does.
And after spending weeks deep inside launcher scripts, patch files and packaging edge cases… boring started sounding really, really attractive.
Honestly?
Yes, probably.
Despite everything in this post, I actually still like the idea behind Electrobun a lot.
The runtime itself was not the problem. In many ways it was refreshingly straightforward. Startup times felt great, Bun is genuinely pleasant to work with, and the smaller runtime story still appeals to me philosophically.
But desktop applications are judged on the parts around the runtime.
Users do not care what JavaScript engine powers your application. They care whether:
Electron has years of ecosystem maturity solving exactly those things. Electrobun is simply not there yet. I would absolutely consider using it again once a few things mature further.
The biggest one for me is installation strategy. Desktop applications should install using conventional OS-native mechanisms.
Not:
“extract this tarball somewhere and run the launcher.”
That may be technically acceptable for developers, but it is not acceptable for mainstream desktop software.
The moment your installer story becomes custom, you inherit years of operational complexity yourself.
Electron ecosystems solved this years ago.
Basic OS integration should not require detective work.
Things like:
should simply work.
Not “work with a helper script”. Not “work if you manually patch the generated output”. Not “work after reading three GitHub issues”.
Just work.
One of the nicest feelings during the Electron migration was deleting workaround code instead of writing more of it. That alone said a lot.
One thing that genuinely surprised me was seeing the documentation suggest bundling Chromium Embedded Framework (CEF) for Linux packaging scenarios.
At that point I had a bit of an existential moment. Because if the solution to packaging problems becomes:
“ship Chromium anyway”
then we’ve effectively lost the entire original value proposition. The whole reason I explored Electrobun was to avoid shipping massive browser runtimes in the first place.
Once we start reintroducing pieces of Chromium manually, things become very difficult to justify operationally.
And honestly, this is probably the fairest conclusion.
Electron is old. Very old.
Which means:
Electrobun simply has not had time to accumulate that maturity yet.
That is normal.
I think the project has potential.
I genuinely do.
But for Skillful, where reliable releases mattered more than runtime ideology, maturity beat elegance. And right now, Electron is still overwhelmingly the more mature choice.