I tried to rewrite Dotvault in Swift. I was solving a problem that didn't exist.
I spent a good chunk of time rebuilding Dotvault natively in Swift, then deleted every line of it. Here's the whole embarrassing story, and why the app you can buy today is still Electron and React.
So I rebuilt a big chunk of Dotvault natively in Swift. SwiftUI, the newest macOS design language, the lot. It looked lovely! And then I deleted every single line of it and went back to the Electron app I already had.
This is the story of how that happened, because I think it’s a useful one. Not the triumphant “and then I saw the light” kind, more the “I spent weeks scratching an itch that was never actually there” kind. If you’ve ever talked yourself into a rewrite, you’ll recognise the shape of it.
Where the itch came from
I build other things. One of them is Edmund, a little capture tool for getting thoughts out of your head without having to decide, right that second, where they need to end up (Linear, Notion, GitHub, wherever). Edmund is native. Swift, SwiftUI, Apple-native from the very first commit. And building it was a genuine pleasure. It feels like the platform, it behaves like the platform, and I got to lean on all the nice system furniture Apple gives you for free.
That success planted a very seductive idea in my head: native is just better. If native felt this good on Edmund, imagine how good Dotvault would feel once I’d done the same thing to it.
That’s the trap, right there. I took a lesson that was completely true for one app and assumed it was a universal law for all of them.
How far I actually got
Further than I’d like to admit! This wasn’t a doodle on a napkin. I made real architectural decisions and wrote real code:
- SwiftUI built into the latest macOS design language, so the app would feel current by construction rather than by a trailing polish pass.
- GRDB for persistence instead of SwiftData, because the storage needs (encrypted blobs, precise migrations, reconciling the database against what’s actually on disk) are exactly where SwiftData fights you.
- CryptoKit and the Keychain to mirror the encryption model the Electron app already uses.
Feature parity was in sight. It genuinely looked promising, and that’s precisely what made it dangerous. A rewrite that’s obviously going badly is easy to abandon. A rewrite that’s going well will happily eat a month of your life.
Where it started fighting back
Two things in particular refused to come along quietly.
The first was parsing. Dotvault lives or dies on how it reads and writes .env files, and there’s a rule I care about a lot: if you parse a file and serialise it straight back out, you should get back exactly what you started with, byte for byte. Comments, blank lines, disabled #KEY=value lines, the lot. In the Electron app that parser is a single piece of plain TypeScript that I share across the whole application and cover with tests. In Swift I was hand-rolling all of it again, and re-earning all those edge cases one by one. All that work, to arrive back at something I already had.
The second was encryption. Every snapshot Dotvault takes is encrypted at rest, and that code is boring in the best possible way: it works, it’s been tested, and I trust it. Porting it to CryptoKit and the Keychain meant rebuilding a security-critical boundary from scratch. Think about that trade for a second. All of the risk of getting encryption subtly wrong, in exchange for a benefit the user would never once see or feel. Nobody has ever bought a .env manager because of which crypto library it links against.
And this is where the thing I’d been quietly ignoring got loud. Electron and React are boring, well-trodden ground. Thousands of people have walked this exact path, which means the hard parts, the parsing, the crypto, the file watching, the diffing, are already solved, and there’s a rich ecosystem of packages sitting right there for me to pull from whenever I need one. Swift meant reinventing a lot of that by hand, on my own, for an audience of nobody who asked.
The bit I’d been avoiding
Here’s the honest core of it. I already had a finished, good-looking product that worked.
The native rewrite wasn’t fixing a problem my users had. It was fixing a problem I had, which was that rewriting things in Swift is fun and selling software is not. Poking at SwiftUI layouts is a lovely way to spend an afternoon. Working out how to actually get Dotvault in front of people who’ll pay for it is hard, unglamorous, and doesn’t give you a satisfying green build at the end. So of course my brain kept steering me towards the fun one and dressing it up as strategy.
There’s a tell I should have spotted earlier. An earlier native attempt got most of the way to feature parity and then stalled, sinking into an endless swamp of trying to make it feel premium. I told myself the second attempt would be different because I’d be more disciplined about it. What I should have taken from that stall was simpler: the whole exercise was a distraction wearing a hard hat.
What Edmund knew that Dotvault didn’t
The difference between the two apps was never the language. It was the starting point.
Edmund was designed native from day one. Every decision, every screen, every interaction was made with the platform in mind from the very beginning. Dotvault wasn’t. Dotvault is a working product with its own history, its own hard-won edge cases, its own shape. A rewrite is not a redesign. You cannot retrofit “native from day one” onto an app that already exists and already works, no matter how nice the toolchain is. You can only rebuild it and hope you don’t drop anything on the way.
So I stopped. I tagged the Swift work, deleted it from the tree, and went back to the app that already does the job.
So, Electron it is
Dotvault is an Electron and React app, and I’m completely at peace with that. It’s a solid, proven, slightly boring base, and boring is a compliment. It means I get to spend my time adding things you’ll actually notice instead of re-solving problems the ecosystem solved years ago. And the promise that matters most hasn’t moved an inch: your secrets never leave your machine. Going native would have gained you nothing there. Not one thing!
The lesson I’m taking away is one I clearly needed to learn the long way round: pick your tools for the job in front of you, not for the job you enjoyed last time. Native was the right call for Edmund. Electron is the right call for Dotvault. Both of those can be true at once.
A thank you for reading this far
If you’ve made it all the way down here, you’ve sat through me admitting I wasted a chunk of time chasing the wrong thing, so the least I can do is make it worth your while.
Use the code Y4MZQ3MA at checkout for 50% off Dotvault. It’s good for the first 50 people who use it, and then it’s gone.
Go and see what all that Electron actually does.