Process architecture
Emdash is built on Electron 30.5.1, which provides a multi-process architecture that combines Chromium and Node.js:- Main process (
src/main/): The Node.js backend that manages the application lifecycle, native OS integration, IPC handlers, database operations, PTY management, and all system-level services - Renderer process (
src/renderer/): The Chromium-based UI layer running React 18 with TypeScript, built using Vite. Handles all user interface, terminal panes, and file visualization - Shared code (
src/shared/): Common utilities, types, and definitions used by both processes, including the provider registry with 21 agent definitions
Why this architecture?
The main/renderer split provides critical security and stability benefits:- Security: Renderer runs in a sandboxed environment with no direct filesystem or system access. All privileged operations go through the main process via IPC
- Stability: UI crashes don’t affect backend operations. PTY sessions and database transactions continue running even if the renderer crashes
- Performance: CPU-intensive operations (git, file operations) run in the main process without blocking the UI thread
Boot sequence
The application bootstraps through three key files:1. entry.ts
src/main/entry.ts is the very first file executed. It performs critical runtime setup:
userData path, otherwise it defaults to ~/Library/Application Support/Electron instead of ~/Library/Application Support/emdash.
Path alias monkey-patch: Since the main process compiles to CommonJS, TypeScript path aliases don’t work at runtime. entry.ts patches Module._resolveFilename to resolve aliases dynamically:
2. main.ts
Afterentry.ts loads, src/main/main.ts performs application initialization:
Environment loading:
gh, codex, claude are found when agents spawn.
App.whenReady() sequence:
3. preload.ts
src/main/preload.ts exposes a secure bridge between main and renderer:
window.electronAPI with full TypeScript safety.
Path aliases
Critical:@/* resolves differently in main vs renderer:
| Alias | Renderer (tsconfig.json) | Main (tsconfig.main.json) |
|---|---|---|
@/* | src/renderer/* | src/* (resolves to src/main/*) |
@shared/* | src/shared/* | src/shared/* |
#types/* | src/types/* | (not available) |
#types | src/types/index.ts | (not available) |
- Renderer: Vite resolves aliases during bundling (ESNext modules)
- Main: TypeScript resolves during compilation, but only for type checking. At runtime,
entry.tshandles resolution
Module system
Main process: Uses CommonJS (module: "CommonJS" in tsconfig.main.json)
- Required by Electron’s Node.js runtime
- Compiled TypeScript outputs
.jsfiles withrequire()andmodule.exports - Native modules (
node-pty,sqlite3,keytar) require CommonJS
module: "ESNext" in tsconfig.json)
- Vite bundles everything for the browser
- Source uses ES6
import/export - Tree-shaking and code splitting enabled
Directory structure
Key configuration files
- tsconfig.json: Renderer/shared config (
module: ESNext,noEmit: true) - tsconfig.main.json: Main process config (
module: CommonJS) - vite.config.ts: Renderer build + Vitest test config
- drizzle.config.ts: Database migration config
- package.json: Build config under
"build"key (Electron Builder) - .nvmrc: Node version lock (22.20.0)
Next steps
- Main process - Deep dive into services, PTY management, and database
- Renderer process - React architecture, hooks, and component structure
- IPC handlers - Communication pattern and handler reference