The Three Main Process Boundaries in Nodeterm's Architecture

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.

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, but runs core services using ServerPlatform (implemented in 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): 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): 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, the Main process forwards tmux output to the UI using centralized channel names:

// 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, the Renderer subscribes through the bridge abstraction:

// 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 to run without modification:

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

export const electronPlatform: CorePlatform = {
  execFile,
  // Desktop-specific implementations
};
// 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:

// 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.
  • 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.
  • The Server process (src/server/) provides headless HTTP/WebSocket access using src/server/platform-server.ts while maintaining API parity with the Main process through 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 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, while the Server process is a pure-Node.js HTTP server that serves the UI to browsers using 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 exposes a restricted API through contextBridge.exposeInMainWorld(), forwarding calls to the Main process via IPC channels defined in 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, 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 to operate without modification across the Main and Server process boundaries.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →