Why We Chose Electron (and We're Not Sorry)
Electron gets a bad rap. For a v0.1 desktop shell that must integrate deeply with local processes, Node.js native modules, and external LLM backends — the alternatives were worse. Here's the trade-off analysis that led to our decision.
TL;DR. We chose Electron for Fabriq Desktop — not because it's trendy, but because the alternatives (Tauri, Flutter Desktop, SwiftUI, a web app) each failed a non-negotiable requirement. Fabriq needs deep OS integration (subprocess management, native Node modules, file system watching), rich IDE-like text rendering, and an accelerated delivery timeline. Electron is the only mainstream framework that delivers all three without requiring us to rebuild half the platform. This post explains the decision matrix and the mitigations we put in place for Electron's well-known downsides.
The accusation
Electron is an easy target. It's been called "a browser that weighs 200MB" and "the worst way to build a desktop app, except for all the others." There's truth in both jokes — but they're not the whole truth.
When we started Fabriq Desktop, we evaluated five approaches:
| Approach | Fat binary | Native feel | OS APIs | Node.js native modules | Dev speed |
|---|---|---|---|---|---|
| Electron | ❌ Heavy | ⚠️ OK | ✅ Full | ✅ Full | ✅ Fast |
| Tauri | ✅ Light | ✅ Good | ⚠️ Rust bridge needed | ❌ No | ⚠️ Medium |
| Flutter Desktop | ✅ Light | ✅ Good | ⚠️ Plugin needed | ❌ No | ⚠️ Medium |
| SwiftUI (macOS only) | ✅ Native | ✅ Best | ✅ Full | ❌ No | ⚠️ Medium |
| Web app only | ✅ None | ❌ No | ❌ None | ❌ No | ✅ Fast |
The heaviest requirement was Node.js native module support. Fabriq integrates with Tree-sitter for AST parsing, spawns subprocesses for dapei-engine, manages git worktrees, and communicates with agent backends via ACP stdio JSON-RPC. All of these either require native Node modules or deep process management. Tauri would have forced us to bridge every system call through Rust. Flutter would have required Dart plugins for every OS-level operation. SwiftUI meant abandoning Windows and Linux.
Electron gave us a direct path: everything we needed worked on day one.
The real decision drivers
1. Tree-sitter and native modules
Fabriq's CDR pipeline depends on Tree-sitter for deterministic code analysis — parsing source files, building code graphs, resolving symbol handles. Tree-sitter is a C library with Node.js bindings (via node-gyp). In Electron, native modules work transparently. In Tauri, they don't — you'd need to rewrite the Tree-sitter integration in Rust or maintain a sidecar process for AST parsing.
// This just works in Electron
import Parser from "tree-sitter";
import TypeScript from "tree-sitter-typescript";
const parser = new Parser();
parser.setLanguage(TypeScript);
const tree = parser.parse(sourceCode);2. Subprocess management
Fabriq runs the dapei-engine as a sidecar process — starting, stopping, communicating over stdio, and health-checking it. Node.js has first-class child_process APIs. In Electron's main process, spawning a subprocess, piping stdout/stderr, and handling exit codes is trivial. Tauri's shell API is more restricted (security by default), and while you can open it up, you're fighting the framework's safety model at every step.
3. File system watching
Fabriq watches workspace directories for changes — new features, updated docs, git state changes. Node.js fs.watch + chokidar is battle-tested. In Tauri, you'd use the fs-watch plugin or implement it in Rust. Both are viable, but neither has the ecosystem maturity of the Node.js file-watching libraries.
4. Rich text rendering
The Feature Workbench renders MDX, syntax-highlighted code blocks, and structured Markdown content. Electron's Chromium renderer handles this natively — the same ecosystem as the web. We use Next.js (via next/mdx-remote), rehype-pretty-code for syntax highlighting, and Tailwind CSS for styling. No native text layout engine needed.
5. Delivery speed
Fabriq was v0.1 of a new category — a workspace operating-system shell. Speed of iteration mattered more than binary size. Electron let us build the entire UI with familiar web technologies while the main process handled the OS-level integration. We shipped a working desktop app in weeks, not months.
The mitigations
We didn't ignore Electron's downsides. We put mitigations in place from day one:
Binary size (200MB baseline)
- Mitigation: Fabriq is a developer tool, not a consumer app. Developers have fast internet and expect Electron-sized binaries from tools like VS Code, Slack, Discord, and Figma. The 200MB download is a one-time cost.
- Future: We tree-shake unused Chromium features via
electron-builderconfig, and we evaluateelectron-trpc— a stripped-down Electron that removes DOM rendering for background processes.
Memory usage
- Mitigation: Fabriq's renderer is lightweight — no IDE features, no language server, no file tree. The heavy lifting (AST parsing, git operations, engine calls) happens in the main process or subprocesses. The renderer is essentially a dashboard + workbench UI.
- Practice: We measured: Fabriq idle uses ~180MB. VS Code idle uses ~400MB. In context, the concern is relative.
Security (XSS surface)
- Mitigation: This was serious enough to warrant an ADR (ADR-0021). The renderer can only invoke read-only engine capabilities directly. All write operations go through typed IPC channels with Zod-validated payloads. A compromised renderer cannot delete repos or modify workspace assets — it lacks the IPC capability to do so.
Platform-specific issues
- Mitigation: We test on macOS (primary), Windows (CI), and Linux (CI). The Electron abstraction layer handles most platform differences. Platform-specific code (window chrome, menu bar, notifications) is isolated behind TypeScript interfaces.
What we'd reconsider in v2
If we were starting Fabriq today, there's one thing we'd evaluate more seriously:
Hybrid architecture: Electron shell + Tauri agents. The Electron shell manages the UI, windowing, and user interaction. Heavy computation (Tree-sitter, CDR pipeline) runs in a Rust-based agent that communicates with the shell over a typed IPC bridge. This would give us Electron's UI ecosystem with Tauri's performance for the compute layer.
But for v0.1, a single-runtime architecture was the right call. A hybrid architecture adds a bridge complexity that would have slowed us down by months without clear user-visible benefit at this stage.
The honest verdict
Electron was the pragmatic choice for Fabriq v0.1, and we don't regret it. The criticisms are real but manageable:
- Download size: Developers are the target audience. They're used to it.
- Memory usage: Our app is lighter than VS Code. Not great, but acceptable.
- Performance: The critical path (agent → engine → disk) doesn't go through the renderer. UI performance is decoupled from computation performance.
What Electron gave us — native Node modules, subprocess management, web-standard UI, fast iteration — was exactly what a v0.1 desktop shell needed. The framework that "everyone hates" shipped the product that everyone else's favorite framework couldn't.
Fabriq is open source (MIT). Browse the architecture on GitHub or download the latest release.
Related articles
Typed IPC: Why Your Electron App Needs a Type-Safe Customs Office
We killed the generic 'run any capability' channel from the renderer after a threat model review. Every IPC call in Fabriq now goes through a typed, Zod-validated handler. Here's the threat model, the migration, and how it makes the app auditable by default.
CDR from dapei-skill to Fabriq Desktop: Four Hard-Earned Moves
CDR keeps code knowledge alive by 'AI as Scanner, Engine as Validator.' In dapei-skill we shipped it as a headless engine powering a static portal. In Fabriq Desktop we kept the philosophy but rewired the architecture around symbol handles, an embedded engine, and a strict workspace/feature boundary.
AI Scanner, Engine Validator: Why We Don't Ask LLMs to Count Lines
Pure static analysis is brittle. Pure LLM analysis hallucinates. Fabriq's third path draws a clean line: Tree-sitter and ASTs answer 'where is X called?' deterministically; LLMs answer 'why is X called?' with fuzzy reasoning. The orchestrator ties them together with evidence chains that are auditable by construction.
