Skip to main content
Back to Blog
Blog

Why We Built MadoHub with Tauri Instead of Electron

The technical reasoning behind choosing Tauri v2 for a desktop development tool — performance, binary size, and Rust's advantages for system integration.

When we started building MadoHub, we had a clear requirement: a desktop application with web-based UI that can manage PTY sessions, read filesystems, and interface deeply with system processes. The two serious options were Electron and Tauri.

We chose Tauri. Here's why, and what we learned.

The binary size argument

The most cited advantage of Tauri is binary size. An empty Electron app is ~150MB because it ships a full Chromium browser. A Tauri app starts under 5MB because it uses the system's native webview (WebKit on macOS, WebView2 on Windows, WebKitGTK on Linux).

For a developer tool, binary size matters more than you'd think. Developers are opinionated about what they install. A 200MB download for a "productivity tool" triggers skepticism. A 10MB download says "this is lightweight and focused."

But binary size was the least important factor in our decision.

The Rust backend

The real reason we chose Tauri is the Rust backend.

MadoHub manages PTY sessions, monitors tmux panes, parses JSONL files, makes API calls for routing, and handles filesystem operations — all concurrently. These are system-level operations that benefit from:

Memory safety without garbage collection: PTY management involves managing process lifecycles, reading file descriptors, and handling signals. In Node.js (Electron's backend), a bug in process management can leak file descriptors or leave zombie processes. Rust's ownership model catches these bugs at compile time.

True concurrency: MadoHub monitors multiple terminal processes simultaneously, polls tmux panes in background threads, and makes concurrent API calls for routing. Rust's async runtime (tokio) handles this with minimal overhead. Node.js can do async I/O, but CPU-bound work (like parsing large JSONL files) blocks the event loop.

Structured error handling: System operations fail in predictable ways — files don't exist, processes exit, API calls timeout. Rust's Result type forces us to handle every failure path. In our routing code, this means the difference between a clean "session file not found, falling back to CWD resolution" and a mysterious undefined error that crashes the routing pipeline.

Our Rust workspace

MadoHub's backend is split into 7 focused crates:

CrateResponsibility
madohub-ptyPTY session management, tmux integration
madohub-paneTmux pane polling for team monitoring
madohub-agentAgent routing, JSONL parsing, API calls
madohub-gitGit operations (status, commit, push)
madohub-teamTeam detection from ~/.claude/teams/
madohub-persistenceCanvas state serialization to ~/.madohub/
madohub-coreShared event emitter trait

This modular structure means each crate can be tested independently, and compile times stay reasonable because unchanged crates aren't recompiled.

The frontend story

Tauri's frontend is a webview rendering a standard web application. We use React 19 + TypeScript + Tailwind CSS + Zustand for state management — the same stack you'd use for a web app.

Communication between frontend and backend happens through Tauri's command system — essentially typed IPC. The frontend calls a Tauri command, the Rust backend executes it, and the result comes back. For streaming data (terminal output, routing events), Tauri provides an event system.

One underrated advantage: the webview uses the system's native rendering engine, which means it inherits the platform's text rendering, scrolling physics, and accessibility features. Text in MadoHub looks native because it is native rendering.

What we gave up

Tauri isn't free of tradeoffs.

Cross-platform webview inconsistencies: WebKit (macOS) and WebView2 (Windows) have different rendering behaviors for edge cases — CSS backdrop-filter behaves differently, certain scroll behaviors vary, and some newer Web APIs land at different times. We've hit a few of these, mostly in animation and blur effects.

Smaller ecosystem: Electron has a decade-long ecosystem of plugins, debugging tools, and community knowledge. Tauri's ecosystem is growing fast but younger. We've had to build some things from scratch that would have been npm packages in Electron.

Rust learning curve: Our frontend team is primarily TypeScript developers. Writing Rust for the backend required ramp-up time. The payoff is worth it for system-level code, but the initial investment is real.

Performance in practice

For a tool like MadoHub — where you might have 5 terminal tiles open, each streaming PTY output, while the routing system monitors for completions and polls tmux panes — performance isn't theoretical. Dropped frames in the canvas, laggy terminal rendering, or delayed routing triggers directly impact usability.

In our benchmarks, MadoHub with 8 active terminal tiles uses ~120MB of RAM. An equivalent Electron app would start at 300MB+ before opening any terminals, just from the Chromium overhead.

CPU usage during normal operation (terminals active, routing idle) stays under 5%. During active routing (API calls + JSONL parsing + content delivery), it spikes briefly to 15-20% and returns to baseline.

Would we choose Tauri again?

Unequivocally yes, for this type of application. The combination of a system-level Rust backend and a web-based frontend is a natural fit for developer tools that need deep OS integration.

For a simpler app — something with no system interactions beyond basic file I/O — the choice is less clear. Electron's maturity and ecosystem size make it the pragmatic choice for many use cases.

But for MadoHub, where the backend manages concurrent PTY sessions, monitors system processes, and parses structured data at high throughput, Rust isn't just a performance optimization. It's a correctness guarantee that lets us ship with confidence.