---
title: How PortPilot is built
description: Electron for the OS, React for the screens, a thin bridge between them. Who is allowed to kill a process, and why.
date: 2026-08-21
slug: how-portpilot-is-built
topic: Desktop
series: PortPilot
seriesOrder: 3
tags:
  - portpilot
  - electron
  - typescript
---

PortPilot is a desktop app because the job is **on the computer**: list ports, kill processes, catch a global shortcut, sit in the menu bar. A website cannot do those things.

## Who may touch the machine

Electron is Chromium (what you see) plus Node (what talks to the OS). The UI does not run OS commands. A **preload** script is the only door.

<ArchitectureDiagram
  label="Who is allowed to touch the machine"
  nodes={[
    { id: 'ui', title: 'Renderer', subtitle: 'React screens', icon: 'apps' },
    { id: 'preload', title: 'Preload', subtitle: 'The only door', icon: 'sdk' },
    { id: 'main', title: 'Main', subtitle: 'Ports, tray, kill', icon: 'terminal' },
  ]}
/>

<InteractiveGui label="Renderer · Preload · Main">
  <ProcessSplitDemo />
</InteractiveGui>

```ts:preload.ts
contextBridge.exposeInMainWorld('portpilot', {
  listPorts: () => ipcRenderer.invoke('ports:list'),
  kill: (pid: number) => ipcRenderer.invoke('ports:kill', pid),
})
```

```ts:main.ts
ipcMain.handle('ports:kill', async (_event, pid: number) => {
  process.kill(pid)
  return { ok: true }
})
```

Kill is more careful in the app (signals, Windows, confirm). The split is the point: **screens in Chromium, sharp tools in Node**.

<Note>
If the renderer could call `process.kill` itself, a bug in a React component could take down random processes. The bridge is a seatbelt.
</Note>

## Stack

| Piece | Job |
|---|---|
| React + TypeScript | Screens and types |
| Zustand | Port list and settings |
| Tailwind | Light / dark, follows the system |
| Fuse.js | `Cmd + K` |
| Bun | Install and scripts |
| electron-vite | Bundle main, preload, renderer |
| electron-log | Crash files after the window is gone |
| electron-updater | GitHub builds update in-app; Store builds go through Apple |

One TypeScript codebase ships Mac and Windows. The cost is a desktop runtime, not a 20kb page. For this product, **one team, two OSes, real process control** beat a thinner Mac-only binary.

Next: the timer behind the list, what we do not scan, and why there is no login.
