← Back to blog
·10 min read·onda

How I built Onda: a Claude Code terminal in 6 months

The build story of Onda — from a frustrated Claude Code user to a notarized macOS terminal with split panes, plugin system and MCP. Architecture decisions, what worked, what didn't.

#onda#build-log#claude-code#mcp#indie-dev

I started building Onda because Claude Code rendered with weird character spacing in the terminal I was using. That's the whole origin story. A subtle font-rendering bug in a Tauri-based terminal turned into six months of work and a notarized macOS app that's now my daily driver.

This post is the build log: the decisions, the architecture, the things I got wrong, and what I'd do differently. If you're building developer tools — or thinking about it — there might be something useful here.

The setup

In late 2025 I was using Claude Code daily across three or four projects. The workflow was already great, but the terminal experience around it was death-by-a-thousand-cuts:

  • One terminal had subtle character spacing issues in TUI apps — vim, Claude Code, htop all looked slightly off.
  • Another had no real split panes — only tabs, which forced an alt-tab dance between agent output and editor.
  • A third was fast but ugly, with no workspace concept and a config file from 2009.
  • The popular cloud-first one wanted a login for features I considered table-stakes.

None of these alone was a deal-breaker. Together, they added up to friction every five minutes during agentic work — which is exactly when you don't want friction. That's the gap that became Onda.

Why a new terminal in 2026 isn't insane

The reflexive response to "I'm building a terminal" is: "Why? There are fifty already." The honest answer is that Claude Code (and AI agents in general) are changing what we do in a terminal. The assumptions baked into terminals from the 1990s no longer fit:

  • We need multiple long-running sessions in view at once — agent + editor + logs + tests — not because we're showing off, but because agents produce output we have to react to immediately.
  • We need rendering correctness for TUI — agents draw box-and-cursor UIs that depend on precise character measurement.
  • We need persistent context per project — the agent's session, the dev server, the editor; coming back tomorrow shouldn't mean rebuilding the layout.
  • We need extension points — small UI for AI status, custom commands, plugin-driven workflows.

iTerm2, Alacritty, kitty are all great terminals. They were not designed around these needs. Warp was, but it baked its assumptions deep into a cloud-first model that I didn't want. There was room for one more.

The first prototype: Tauri (and why I scrapped it)

The first version of Onda was a Tauri app. Rust backend, web frontend. I built it to MVP in roughly three weeks: tabs, basic split, theming, a small plugin demo. It was lean — installer was 8MB, RAM usage tiny.

Then I tested it with Claude Code.

The TUI rendering was broken in a subtle way: characters had extra horizontal spacing that made box-drawing characters not align. Vim in the same terminal had the same problem. The cause was buried in how Tauri's WebView handled font metrics on macOS, interacting badly with xterm.js's assumptions about character measurement.

I spent two weeks chasing it. Tried different fonts. Tried patching xterm.js. Tried different WebView settings. The bug was reproducible but the fix involved either patching Tauri itself or working around at a layer that would break on every Tauri update.

That was the moment I made the most expensive decision of the project: scrap the Tauri version, rebuild on Electron.

Why Electron was the right call (even though it hurt)

Saying "I'm rewriting on Electron" felt like a step backwards. Electron is the punching bag of native developers — heavy, RAM-hungry, "just a browser". I knew this. I rewrote on Electron anyway because:

  1. Chromium's font rendering on macOS is correct for TUI. xterm.js works exactly as designed. No invisible spacing, no font-metric bugs.
  2. The ecosystem is mature. node-pty for the PTY layer, electron-builder for codesigning and notarization, electron-updater for auto-update. These are battle-tested.
  3. The performance gap matters less than I thought. 200MB of RAM on Apple Silicon is a rounding error. The CPU cost is dominated by network I/O when running an agent.
  4. I could ship in 6 weeks instead of 6 months.

The rewrite took 5 weeks. Onda has been Electron since.

The lesson: pick the boring technology that solves your actual problem. I lost 5 weeks on Tauri because I wanted Onda to be "modern". The users don't care about your stack — they care about whether vim looks right.

Architecture overview

Onda's architecture is simpler than it looks. Three layers:

Main process (Electron Node.js)

Owns the lifecycle: windows, menus, auto-update, codesigning, IPC routing. Spawns one PTY per pane via node-pty. PTYs are managed in a registry keyed by pane ID. When a pane is destroyed, the PTY is killed; when a workspace is closed, all its PTYs go.

This is the boring layer. ~1500 lines, few abstractions, lots of error handling.

Renderer process (React + xterm.js)

The UI layer. Each pane is an <XTerm> component bound to one PTY via IPC. The mosaic split layout is a recursive tree of horizontal/vertical splits, persisted to disk per workspace. React handles the tree and the focus model; xterm.js handles cells, ANSI, and selection.

The interesting work here is the focus management: with N panes, only one has the keyboard. The cursor follows the focused pane, copy/paste targets the focused pane, font-size shortcuts apply to the focused pane. Getting this right took several iterations.

Plugin system (Web Workers)

Plugins are JavaScript bundles that run in separate Web Workers, not in the renderer. They communicate via a message-passing API. They can:

  • Subscribe to pane events (pane created, command run, pane focused)
  • Render small UI in the status bar (a worker draws to an OffscreenCanvas, the renderer composites it)
  • Expose commands that show up in the command palette

The reason for the Worker isolation is security and stability: a misbehaving plugin can't crash the renderer or steal renderer DOM. The reason for the message-passing API is future-proofing: if I ever rewrite the UI, the plugin contract stays stable.

This is the part of Onda I'm most proud of architecturally. The plugin system is documented in detail at onda.mindfullabai.dev and several plugins are already in the gallery.

MCP: the integration that took the longest

The hardest single feature in Onda is the MCP (Model Context Protocol) integration. MCP is Anthropic's protocol for letting language models call tools through a structured server interface. Claude Code uses MCP to expose tools to the model. Onda exposes its own MCP server so external agents can talk to the terminal directly.

Why this matters: with MCP, an agent running outside Onda (a Telegram bot, a webhook, a CI pipeline) can ask Onda things like "what's running in the user's project pane?" or "open a new pane and run X command". This turns the terminal from a passive UI into an addressable, programmable surface.

The integration took roughly 3 weeks of focused work. Most of it was figuring out the lifecycle: when does the MCP server start? Per-workspace or global? How do we authenticate clients? How do we handle stale connections when Onda restarts?

The answer in the current shipping version: one MCP server per workspace, started lazily when an external client first connects, with token-based auth stored in the macOS Keychain. The full protocol details are in the Onda MCP documentation.

If you're building a developer tool and want it to be addressable by AI agents, MCP is the protocol to implement. It's an emerging standard, but it's the right level of abstraction.

What didn't work

A few features I tried and removed:

1. AI autocomplete in the terminal. I shipped a version that called Claude in the background to suggest completions for shell commands. It looked great in the demo. In daily use it was annoying — the suggestions were either obvious or irrelevant, and the latency made everything feel sluggish. I removed it after two weeks.

2. A built-in package manager UI. I prototyped a UI for brew/npm that surfaced installs in a sidebar. Nice idea, but the brew/npm CLIs are already excellent and people don't want a GUI for them. Killed before merging.

3. Cloud sync for workspaces. I built half a sync system before realizing that workspaces should not sync across machines. Your dev machine and your laptop have different paths, different installed tools. Syncing layout would be helpful; syncing actual workspace state was a category error.

4. Tab groups. Added them, removed them. Workspaces already do the same job at the right level of abstraction.

The pattern across all four: don't add features for hypothetical use cases. Add them when you, the dogfood user, would actually use them daily. Then ask yourself again, after two weeks of use, if you're using them. If not, kill them.

The shipping cadence

Onda has been on a roughly weekly release cadence since launch. Every release goes through:

  1. Local testing in my own daily workflow (typically 2-3 days).
  2. Notarization with Apple (~30 minutes).
  3. Auto-update broadcast to existing users via electron-updater.

The notarization step is unsexy but critical. macOS won't let unsigned apps run easily. If you're shipping a Mac app and skip codesigning + notarization, you're effectively asking users to right-click → Open → confirm — and most won't.

The current version is Onda 1.8.0, shipped May 2026. It introduces the MCP server, an updated split-pane keyboard model, and three new plugins in the gallery.

What I'd do differently

If I started over today, I would:

  1. Skip the Tauri prototype. Pick Electron from day one. The TUI rendering issue is a known constraint; don't pretend you'll be the one to fix it.
  2. Build the plugin system earlier. I bolted it on around month 4. If it had been the foundation from day 1, several core features would have been plugins themselves (the command palette, the workspace switcher), making the codebase smaller.
  3. Set up GitHub Sponsors / paid tier from launch. I added the Pro tier in month 5. The signal-to-noise on free vs. paid users is dramatically better; you understand who actually wants the tool.
  4. Write more publicly. I shipped four months before writing a single blog post. Almost all the early traction came from one Reddit thread; if I'd had a blog with three or four useful posts, the traction would have compounded faster.

That last point is partly why this blog exists.

Where Onda goes next

The roadmap for the next few months:

  • Onda for Linux (no commitment yet, but the Electron path makes it cheap-ish)
  • Plugin gallery 2.0 with one-click install
  • Better MCP discovery — letting external agents introspect Onda's capabilities without out-of-band documentation
  • Theme marketplace because everyone keeps asking

If you want to follow the build, the RSS feed of this blog has all the build logs and engineering posts. If you want to actually use Onda, download it free — it's on macOS, notarized, and the MCP integration in 1.8.0 is the most interesting thing I've shipped this year.

A small thanks

Onda exists because of one Reddit comment thread where someone said "yeah, Claude Code rendering is broken in my terminal too, can someone fix this". That thread → the prototype → the rewrite → the plugin system → MCP → the shipping product is a 6-month chain.

If you've used Onda, written about it, or just complained about a bug — thank you. The next 6 months are funded entirely by your interest. If you haven't tried it, now's a good moment.

Subscribe to the RSS feed for the next posts in this series — including a deep-dive on the plugin Web Worker architecture and a comparison of Onda's approach to terminal extensibility versus iTerm2 and Warp.


Our flagship

Onda — terminal for Claude Code & AI agents.

Free, native macOS. Split panes, workspaces, plugin system. Made for developers who live in the terminal with an agent.

Try Onda free