GitHub: https://github.com/ESeungJun/smash · https://github.com/ESeungJun/poketmon_idle
I've built a handful of desktop apps this year, and most of them are Electron apps. Every installer ended up over 100MB. Smash!, an app that tidies my Downloads folder, was 114MB. A desktop pet that grows while I focus was 198MB. Then I built a weather widget with Tauri, and it came out at 17MB, 3D assets included. (I wrote this with help from Claude and checked every number myself.)
So I moved two of the Electron apps to Tauri, which means Rust on the native side. I did it together with Claude Code. This post covers how I picked the order, and what I did so the people already using these apps wouldn't notice anything except a smaller download.
The numbers
On the Mac, the pet app went from 198.7MB to 5.6MB, and Smash went from 114MB to 6.2MB. Smash used to ship separate builds for Apple silicon and Intel. Now it's one universal build at 6.2MB. On Windows, the installers went from 92.8MB to 2.0MB and from 115MB to 2.2MB.
I could only compare memory for Smash. The Electron version used 244MB across 8 processes. The Tauri version uses 88MB.
That 88MB needs a note. Tauri draws the UI with the system WebKit on macOS, and WebKit runs in its own processes outside the app. My first measurement only counted the app process and gave me 48MB. That felt like cheating, so the number above adds the three WebKit processes that started with it.
Which app first
Before touching any code, I went through my Electron apps one by one. "Port it to Rust" sounds like a big rewrite. In practice, the UI (HTML or React) stays almost the same, and you rewrite the Electron main process, the Node part, in Rust.
So I counted how big each main process was and which Electron features it used.
The pet app's main process was only 494 lines, and everything it used (tray, transparent window, always-on-top) has a Tauri equivalent. Smash was bigger, with 1,816 lines plus an MCP server. DroidLens, an Android dev tool of mine, has over 5,000 lines to rewrite, and install size matters less for a dev tool. I put it on hold.
I also wrote down what I expected to get: smaller installers, less memory, faster startup. I didn't expect features to get faster just because of Rust. My apps were never slow because of JavaScript. The cost I accepted was that each OS now uses its own webview (WebKit on macOS, WebView2 on Windows), so the UI can look slightly different per OS.
The plan was simple. Port the smallest app, measure, and only move on to Smash if the numbers held up. I'd rather pick the next app from a measured number than from an estimate.
Goals
I used three goals to make decisions along the way.
The first one mattered most. A smaller installer is nice, but if updating wipes your rules or quietly breaks your Claude setup, it's a step back.
Keep the UI, match the boundary
I barely touched the UI code in either app.
In Electron, the UI talks to the main process through an object like window.electronAPI or window.tidy. I wrote one small file that builds the same object on top of Tauri commands. The UI calls the same functions and gets the same values back, so it doesn't need to know whether Electron or Rust is on the other side.
For the pet app, all 38 React components stayed as they were. The 494-line main process became 720 lines of Rust, and the installer dropped from 198.7MB to 5.6MB. That was enough to move on to Smash.
Smash, MCP server included
Smash had more to port: the rule engine, the folder watcher, trash handling, history, the Claude integration, and an MCP server. I started after midnight and shipped 2.0.0 around noon the same day. The Rust side is 3,841 lines, tests included.
The MCP server is the biggest change. You can register Smash as an MCP server in Claude Code or Claude Desktop and say "tidy my Downloads". In the Electron version, the server ran by starting the bundled Electron binary as if it were Node. Now it's the app binary with --mcp, and Node isn't involved at all.
That created a problem, though. Anyone on 1.x had Claude configured with the old Electron path. The moment they updated, that path would disappear, and Claude would silently lose Smash.
So the first time 2.0 starts, it looks for Claude config files and rewrites any old Smash entry to point at the new binary. That covers ~/.claude.json, the Claude Desktop config, and per-profile config folders. It leaves places where Smash isn't registered alone, and it skips the rewrite when the app runs from a temporary path, like straight from the disk image.
What the tests didn't catch
I ported the existing tests for the rule engine and file moves to Rust, and all 21 pass. A few things still slipped past them.
My edit reshuffled the user's Claude config. When I first built the registration fix, a review showed that reading and rewriting the JSON sorted every key alphabetically. It still worked. But ~/.claude.json holds a person's whole Claude Code setup, and changing one entry shouldn't reorder the entire file. I switched to keeping the original key order. Then I compared three real config files on my machine before and after, and only the smash entry had changed.
The nag banner lost the folder name. When auto-tidy runs, Smash shows a banner like "Tidied 3 files in Downloads". After the port, I dropped a real file into the folder and got "Tidied 1 file in" with no folder name. The watcher was passing a path where it should have passed the folder's ID, and no test looked at that value. I fixed it and made the test check the ID.
Building only in CI made me find errors late. At first this Mac had no Rust, so every build ran on GitHub Actions. That meant waiting over ten minutes to learn about a single compile error. (Not my proudest afternoon.) I installed Rust locally, and now I build and test on my machine first. CI only does the things I can't do here, like the Windows build.
The first two only showed up with the installed app, my real config files and real files. So after porting, I left the installed app running for over 40 seconds, measured memory, and talked to the MCP server directly before calling it done.
No more manual downloads
Neither app had auto-update in its Electron days. Every release meant downloading again.
Both apps now check 15 seconds after launch and every 6 hours after that. When there's a new version, they download it in the background and add "Install new version and restart" to the tray menu. They only install when you click it. That rule comes from the pet app: it's a focus timer, and it shouldn't restart in the middle of your focus session.
I checked that a test build actually updated itself to the real 2.0.0. In 2.1.0 I also added the current version and a "Check for updates" button to the settings screen.
What's left
- On Windows, I've only confirmed that the installer builds. I haven't run it on a real Windows machine yet, and WebView2 may render the UI a little differently.
- Next in line is Claudy, a desktop pet that shows what my Claude sessions are doing. Its main process is 2,799 lines. With numbers like these, I think it's worth doing.
What I learned
- Size and memory dropped a lot. Feature speed didn't change in any way I could feel, which is what I expected.
- The Rust rewrite wasn't the hard part. Carrying over settings, history and Claude registrations without breaking anything took more care.
- Porting the smallest app first and measuring it made the next decision easy.
If one of these apps sounds useful, a star on GitHub would make my day.
- Smash: https://github.com/ESeungJun/smash
- Pokémon Idle: https://github.com/ESeungJun/poketmon_idle
- LinkedIn: https://www.linkedin.com/in/승준-이-53707a328/




