# The Three Main Process Boundaries in Nodeterm's Architecture

> Discover the three main process boundaries in Nodeterm: Main, Renderer, and Server. Understand how this architecture supports desktop, server, and mobile development while isolating platform code.

- Repository: [eneskirca/nodeterm](https://github.com/eneskirca/nodeterm)
- Tags: architecture
- Published: 2026-08-25

---

**Nodeterm separates concerns across three distinct process boundaries—the Main process (`src/main/`), Renderer process (`src/renderer/`), and Server process (`src/server/`)—enabling the same core logic to run on desktop, server, and mobile while keeping platform-specific code isolated.**

Nodeterm is an open-source terminal workspace that unifies tmux, terminals, and kanban boards into a single React Flow canvas. Its architecture is built around three well-defined process boundaries that separate Node.js system access, Chromium-based UI rendering, and headless server capabilities. These boundaries ensure that platform-specific implementations remain isolated while the core business logic stays portable across deployment environments.

## The Three Core Process Boundaries

### Main Process

The **Main process** resides in `src/main/` and serves as the Node.js/Electron entry point for desktop deployments. It manages native window creation, system menus, file-system access, and tmux integration. This process hosts the platform-agnostic **Core** services through the `CorePlatform` implementation defined in [`src/main/platform-electron.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/main/platform-electron.ts).

### Renderer Process

The **Renderer process** located in `src/renderer/` executes within a Chromium sandbox and renders all UI components—including React Flow diagrams, terminal nodes, and kanban boards. All UI code runs exclusively in this context and communicates with system-level functionality **only** via the bridged API exposed in `window.nodeTerminal`. This strict isolation prevents direct access to Node.js APIs from the UI layer.

### Server Process

The **Server process** in `src/server/` provides a headless deployment option as a pure-Node HTTP and WebSocket server. It serves the built UI assets to browsers and implements the identical IPC contract defined in [`src/shared/rpc.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/rpc.ts), but runs core services using `ServerPlatform` (implemented in [`src/server/platform-server.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/server/platform-server.ts)) instead of the Electron-specific `CorePlatform`.

## How the Process Boundaries Interact

Three architectural mechanisms coordinate these boundaries while maintaining strict separation of concerns:

1. **Core Services Abstraction** (`src/core/`): Contains platform-independent logic for PTY management, workspace handling, and AI agents. Both the Main and Server processes import these modules and provide thin platform-specific shims.

2. **Preload Bridge** ([`src/preload/index.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/preload/index.ts)): Runs in the Electron preload context to safely expose a narrow API via `contextBridge.exposeInMainWorld('nodeTerminal', ...)`. This forwards Renderer calls to the Main process through IPC. The Server Edition reimplements this bridge over WebSocket in `src/renderer/bridge/`.

3. **Shared IPC Channels** ([`src/shared/ipc.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/ipc.ts)): Central definitions of constants like `CHANNEL_PTY_DATA` and `CHANNEL_PTY_WRITE` ensure the Renderer uses identical channel names whether communicating with Electron's `ipcMain` or the Server's WS-RPC layer.

## Implementation Examples Across Boundaries

### Main-to-Renderer Communication via IPC

In [`src/main/main.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/main/main.ts), the Main process forwards tmux output to the UI using centralized channel names:

```typescript
// src/main/main.ts
import { ipcMain } from 'electron';
import { CHANNEL_PTY_DATA } from '@/shared/ipc';

ipcMain.on('pty:create', (event, sessionId) => {
  const pty = ptyManager.create(sessionId);
  pty.on('data', data => {
    event.sender.send(`${CHANNEL_PTY_DATA}:${sessionId}`, data);
  });
});

```

In [`src/renderer/terminal/TerminalNode.tsx`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/terminal/TerminalNode.tsx), the Renderer subscribes through the bridge abstraction:

```typescript
// src/renderer/terminal/TerminalNode.tsx
import { useEffect } from 'react';
import { CHANNEL_PTY_DATA } from '@/shared/ipc';
import { useNodeBridge } from '@/renderer/bridge';

export function TerminalNode({ id }: { id: string }) {
  const { on } = useNodeBridge();

  useEffect(() => {
    const handler = (data: string) => term.write(data);
    on(`${CHANNEL_PTY_DATA}:${id}`, handler);
    return () => off(`${CHANNEL_PTY_DATA}:${id}`, handler);
  }, [id, on]);
}

```

### Platform-Specific Core Implementations

Both processes satisfy the same `CorePlatform` interface, allowing [`src/core/pty-manager.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/pty-manager.ts) to run without modification:

```typescript
// src/main/platform-electron.ts
import { CorePlatform } from '@/core/platform';
import { execFile } from 'child_process';

export const electronPlatform: CorePlatform = {
  execFile,
  // Desktop-specific implementations
};

```

```typescript
// src/server/platform-server.ts
import { CorePlatform } from '@/core/platform';
import { execFile } from 'child_process';

export const serverPlatform: CorePlatform = {
  execFile,
  // Server-only stubs for UI-related methods
};

```

### Secure Bridge Exposure in Preload

The preload script restricts Renderer access to specific, allowlisted operations:

```typescript
// src/preload/index.ts
import { contextBridge, ipcRenderer } from 'electron';
import { CHANNEL_PTY_WRITE } from '@/shared/ipc';

contextBridge.exposeInMainWorld('nodeTerminal', {
  write: (sessionId: string, data: string) => {
    ipcRenderer.send(`${CHANNEL_PTY_WRITE}:${sessionId}`, data);
  },
  on: (channel: string, listener: (...args: any[]) => void) => {
    ipcRenderer.on(channel, (_e, ...args) => listener(...args));
  },
  off: (channel: string, listener: (...args: any[]) => void) => {
    ipcRenderer.removeListener(channel, listener);
  },
});

```

## Summary

- The **Main process** (`src/main/`) handles Electron-native operations including window management and tmux integration, hosting core services via [`src/main/platform-electron.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/main/platform-electron.ts).
- The **Renderer process** (`src/renderer/`) executes all UI code in a Chromium sandbox and communicates exclusively through the `window.nodeTerminal` bridge exposed in [`src/preload/index.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/preload/index.ts).
- The **Server process** (`src/server/`) provides headless HTTP/WebSocket access using [`src/server/platform-server.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/server/platform-server.ts) while maintaining API parity with the Main process through [`src/shared/rpc.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/rpc.ts).
- **Core services** (`src/core/`) remain platform-agnostic, enabling identical PTY and workspace logic to function across all three boundaries.
- **IPC channels** defined in [`src/shared/ipc.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/ipc.ts) guarantee consistent communication protocols whether running in desktop Electron or browser-based server environments.

## Frequently Asked Questions

### What is the difference between the Main and Server processes in nodeterm?

Both implement the same `CorePlatform` interface and host identical core services from `src/core/`, but the Main process is Electron-specific and manages native windows via [`src/main/platform-electron.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/main/platform-electron.ts), while the Server process is a pure-Node.js HTTP server that serves the UI to browsers using [`src/server/platform-server.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/server/platform-server.ts) and WebSocket transport.

### How does the Renderer process securely access system resources?

The Renderer never accesses system resources directly. Instead, [`src/preload/index.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/preload/index.ts) exposes a restricted API through `contextBridge.exposeInMainWorld()`, forwarding calls to the Main process via IPC channels defined in [`src/shared/ipc.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/ipc.ts). This prevents unsafe Node.js access from the Chromium sandbox.

### Can nodeterm's UI code run unchanged in both desktop and server modes?

Yes. The Renderer components in `src/renderer/` are environment-agnostic. When running in Server mode, `src/renderer/bridge/` reimplements `window.nodeTerminal` using the WS-RPC protocol from [`src/shared/rpc.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/rpc.ts), allowing identical React components to function in both desktop Electron and browser contexts without modification.

### What role does the CorePlatform interface play in nodeterm's architecture?

`CorePlatform` is the abstraction layer defined in the core module that standardizes platform-specific operations like process execution. Both `electronPlatform` and `serverPlatform` satisfy this interface, enabling core modules such as [`src/core/pty-manager.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/pty-manager.ts) to operate without modification across the Main and Server process boundaries.