Understanding Nodeterm's Process Models: Main, Core, Server, Renderer Explained
Nodeterm uses a three-process runtime architecture — Main (Electron), Core (platform-agnostic service layer), and Server (headless backend) — plus Preload and Renderer glue layers, to keep the same terminal logic running identically across desktop and browser environments.
The eneskirca/nodeterm repository implements a terminal emulator with a desktop application and a Server Edition. Its process model is designed around one key principle: core logic never touches Electron or UI code. This article walks through each process, where it lives in the source tree, and how these layers interoperate.
Overview of the Three Runtime Processes
Nodeterm separates execution into three distinct runtime processes, each with a specific responsibility:
| Process | Responsibility | Source location | Launch method |
|---|---|---|---|
| Main | Electron shell, window management, native dialogs | src/main/ |
electron . |
| Core | Platform-agnostic business logic (terminal, pty, workspaces, git) | src/core/ |
Imported by Main/Server |
| Server | Headless HTTP + WebSocket backend for browsers | src/server/ |
npm run server:dev |
Two additional layers — Preload and Renderer — connect the UI to these processes without coupling the frontend to backend code.
The Main Process: The Electron Desktop Shell
The Main process is the entry point for the desktop application. It owns the Electron BrowserWindow and is responsible for OS-level integration such as file dialogs, app menus, and tmux discovery.
All desktop behavior lives under src/main/. The entry point is src/main/index.ts. It creates the window, wires the preload script, and installs the core factory.
The CorePlatform Implementation
The Main process provides a concrete implementation of the abstract platform interface. In src/main/platform-electron.ts, you'll find the Electron-specific behaviors: dialog calls, atomic file writes, and tmux detection.
The architecture boils this down to dependency injection:
// src/main/index.ts (simplified)
import { CorePlatform } from '../core/platform';
import { createCore } from '../core/core-factory';
const platform: CorePlatform = {
...require('./platform-electron')
};
const core = createCore(platform);
// core is the fully wired instance — one API, many platforms
The key point: createCore accepts any implementation of CorePlatform. The Main process supplies the Electron one; the Server process supplies its own. Both end up running identical core logic.
The Core Process/Module: Platform-Agnostic Logic
The Core process (in some repo docs called the core module) is the heart of Nodeterm. It contains all platform-agnostic behavior:
- Terminal/PTY management
- Workspace persistence and session tracking
- Git helper operations
- Agent hooks for AI integration
- Scrolling and terminal tokenization
- Shared type definitions for messages and protocol
Source files live in src/core/, including src/core/platform.ts, which exports the CorePlatform interface that both Main and Server satisfy.
What the Core Never Does
According to the repository's deep reference (CLAUDE.md), the core's invariants are explicit:
- Core is first-class everywhere. The same services drive the desktop app, Server Edition, and future remote transports.
- POSIX-only features degrade explicitly. Features depending on tmux or SSH control-master throw clear
EACCES/EPERMerrors on Windows instead of failing silently. - Platform neutrality is the default. All file-system, process-spawning, and networking helpers go through
CorePlatform.
Direct Node.js or Electron APIs appear only in src/main/, src/server/, and their platform implementations — never inside src/core/.
The Server Process: Headless HTTP + WebSocket
The Server process runs Nodeterm as a headless backend. It provides an HTTP + WebSocket service that serves the compiled React renderer to browsers. This process runs as plain Node — no Electron.
The entry point is src/server/main.ts. It creates the HTTP server, wires the WebSocket RPC bridge (src/server/ws.ts), and instantiates the core through the ServerPlatform implementation in src/server/platform-server.ts.
// src/server/main.ts
import { CorePlatform } from '../core/platform';
import { createCore } from '../core/core-factory';
import { platformServer } from './platform-server';
const core = createCore(platformServer as CorePlatform);
// The Server process runs the identical core logic without Electron.
The Server process gives Nodeterm a path for collaboration and remote access since the same UI — the renderer process — runs inside both the desktop browser and a web browser tab.
The Renderer and Preload: Glue Layers
While Main, Core, and Server are the runtime processes, the Renderer and Preload layers bridge the gap to the UI.
Renderer — the React frontend
The renderer lives in src/renderer/. This is a standard React app. All UI code communicates with the native side only through window.nodeTerminal, a bridge object.
// src/renderer/some-component.tsx
const nodeTerminal = (window as any).nodeTerminal;
// Calls are forwarded to the Main (desktop) or Server (browser) process.
nodeTerminal.sendText('ls\n');
The renderer never directly imports src/core modules or src/main modules. That separation is what makes the same UI work both inside Electron and over a WebSocket in a browser tab.
Preload — secure bridge
The preload script in src/preload/index.ts runs in the privileged context between the renderer and the Main process. It uses Electron's contextBridge to expose a narrow API via window.nodeTerminal. This function:
- Whitelists actions (sendText, resize, subscribe)
- Forwards calls from the renderer to the Main process via IPC
- Never exposes remote Electron APIs to the renderer
Testing the Separation
Because new code is platform-neutral by default, the process model also enables clean tests:
- Unit tests hammer the pure core logic — no Electron, no Node.
- Integration tests spin up a minimal Main or Server process to verify IPC wiring and the bridge.
This keeps the core deterministic and satisfies the repository's invariants without forcing an Electron window in CI.
Code Workflow at a Glance
- Renderer dispatches a user action (e.g., sending a command) via
window.nodeTerminal. - Preload signs the request and serializes it to IPC (desktop) or WebSocket (server).
- Main or Server receives that command and calls the corresponding
CorePlatformmethods. - Core executes the actual business logic — spawns a PTY, writes to the terminal, updates the workspace store.
- The result flows back down the same wire to the renderer.
This unidirectional flow prevents dead cycles and keeps the renderer safe.
Summary
- Nodeterm's codebase is split into Main (Electron shell), Core (platform-agnostic business logic), Server (headless backend), plus Preload and Renderer as glue layers.
- The
CorePlatforminterface (src/core/platform.ts) is the contract that desktop and server both implement; this is the central abstraction for process modularity. - Core never touches Electron or UI code — all such edges stay in
src/main/andsrc/server/. - The renderer talks only through the
window.nodeTerminalbridge, making the React app runnable both in Electron and in a browser via the Server Edition.
Frequently Asked Questions
What is the relationship between nodeterm's core and the main process?
The Main process plugs an Electron-specific implementation of CorePlatform into the core factory. The core orchestration and scalability logic remains untouched by the OS specifics — the core runs identically inside the Main process and the Server process.
How is the renderer isolated from Node.js internals?
The renderer never imports core modules. It accesses everything through the window.nodeTerminal preload bridge, which forwards calls to the Main or Server process over IPC or WebSocket. That isolation keeps the React code portable.
When should I use the Server process instead of the Main process?
When you want to run Nodeterm in a browser — headless mode — without Electron. The Server Edition starts via npm run server:dev and exposes the same UI on HTTP with WebSocket transport. All core features stay available, but native desktop helpers (dialogs, tmux installation) are replaced by the ServerPlatform implementation in src/server/platform-server.ts.
Why does the architecture emphasize CorePlatform in the FAQ and docs?
It is not unique to Nodeterm — it is an ordinary, simple DI pattern. But here it's the key to the process model:
- Define behavior (interface in
src/core/platform.ts) - Implement once for Electron, once for server
- Existing code in
src/core/never cares which implementation is behind
It keeps the business logic in one place and lets the renderer service both desktop and web.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →