← All posts

The Stack Behind MOLTamp: Electron, React, Vite, and Why

Every indie dev has opinions about Electron. Here are mine, plus why I chose React and Vite for a terminal shell that needs to feel native.

Every time I mention Electron in a developer conversation, someone winces. "Electron is bloated." "Why not Tauri?" "Just use Swift."

I get it. I had the same reaction. Then I actually tried to build a skinnable terminal cockpit and learned why Electron is still the right tool for this specific job. Here's the honest breakdown.

Why Electron

MOLTamp needs to do three things that are genuinely hard:

  1. Spawn and manage a PTY (pseudo-terminal) to run Claude Code, Codex CLI, and other AI agents as real terminal processes
  2. Inject arbitrary CSS from community-authored skins into a sandboxed rendering context
  3. Ship on macOS today with Windows and Linux coming soon

node-pty gives me the PTY layer in Node.js. Chromium gives me the CSS rendering engine. Electron gives me both in one package with a mature IPC bridge between them.

Could I do this in Tauri? Maybe. Tauri uses the system WebView, which means CSS rendering varies across platforms — the exact thing a skinning system can't tolerate. A skin that looks perfect on macOS WebKit might break on Windows WebView2. Chromium is Chromium everywhere. For a product whose entire value proposition is "your skin looks exactly how you designed it," rendering consistency isn't optional.

Could I do this in Swift? Absolutely — for macOS only. And I'd lose the community of web developers who can author skins with CSS they already know. The skinning guide is literally "write CSS variables in ." Try getting that simplicity with a native toolkit.

Why React

The cockpit UI has a lot of state: which skin is active, which panels are visible, what the terminal is doing (idle, thinking, streaming, tool use), what widgets are loaded, what the telemetry says. React + Zustand gives me a clean store-driven architecture where each concern (skin store, session store, telemetry store, event store) is independent but composable.

I considered Svelte and Solid. Both are great. But React's ecosystem for this kind of widget-heavy, plugin-style UI is unmatched. When a community member wants to build a custom widget, they're writing React. That's a larger talent pool than any alternative.

Why Vite

Create React App is dead. Webpack is slow. Vite does what I need: fast dev builds, clean production bundles, and TypeScript support out of the box. The Electron integration is a bit manual (no HMR in our setup — we do full restarts), but the build is sub-2-seconds and that's fast enough.

The Unexpected Win: CSS as the Plugin System

The biggest architectural decision wasn't the framework — it was making CSS the entire skinning API. No JavaScript in skins. No React components. Just CSS custom properties.

This means:

  • Skins are safe by default (CSS can't access the filesystem or network)
  • Skins are portable (every web developer already knows CSS)
  • Skins are validatable (we can scan the CSS and flag issues before the user installs)
  • Skins are composable (a skin's variables become toggles in the Effects panel automatically)

The validator we built scans every uploaded skin for ungated visual effects — animations that can't be turned off, box-shadows without toggle variables, backdrop filters that run permanently. If it finds issues, it generates an AI prompt the author can paste into Claude to get the fix. The whole pipeline is CSS in, CSS out.

What I'd Do Differently

If I started over:

  • I'd use Vite's library mode for the widget SDK from day one instead of retrofitting it
  • I'd invest in end-to-end tests earlier (our first 6 months were manual testing only)
  • I'd build the skin validator before the first community upload, not after

But the core stack — Electron + React + Vite + CSS variables — I wouldn't change. It's the right set of trade-offs for a product that needs native-feeling performance, web-standard extensibility, and cross-platform consistency.

If you're thinking about building something similar, start with the CSS variable contract. Get that right and everything else follows.