Architecture deep-dive
This page goes deeper than the user-facing Architecture doc. If you’re contributing code, start here.
Process model
Section titled “Process model”Two processes:
- Bun main process (
src/bun/) — owns PTYs, parses sideband channels, polls process metadata, exposes RPC over Electrobun + Unix socket + WebSocket. - Electrobun WebView (
src/views/terminal/) — renders xterm.js, the sidebar, the Process Manager, canvas overlays, browser panes.
There is no other server, no daemon, no helper process.
Directory roles
Section titled “Directory roles”src/├── bun/ # Bun main process│ ├── index.ts # BrowserWindow, RPC handlers, socket server, poller wiring│ ├── session-manager.ts # Multi-surface PTY manager│ ├── pty-manager.ts # Single PTY, Bun.spawn with terminal: true│ ├── browser-surface-manager.ts # Browser surface state│ ├── browser-history.ts # JSON-persisted browser history│ ├── sideband-parser.ts # Multi-channel JSONL + binary reader│ ├── event-writer.ts # fd 5 JSONL event writer│ ├── socket-server.ts # Unix socket JSON-RPC server│ ├── rpc-handler.ts # Dispatcher merging per-domain handlers│ ├── rpc-handlers/ # system / workspace / surface / sidebar / pane / notification / agent / browser-* / telegram│ │ └── shared.ts # METHOD_SCHEMAS + validateParams + geometry helpers│ ├── surface-metadata.ts # 1 Hz poller + ps/lsof parsers + diff│ ├── settings-manager.ts # Load/save with debounced persist│ ├── web/ # Web mirror server│ │ ├── server.ts # Bun.serve, envelopes, resume, auth│ │ ├── connection.ts # SessionBuffer (ring buffer, seq, backpressure)│ │ └── state-store.ts # Server-side cache of metadata/panels/sidebar│ └── native-menus.ts├── shared/│ ├── types.ts # RPC schema, sideband types, ProcessNode, SurfaceMetadata│ └── settings.ts # AppSettings schema + validation + theme presets├── views/terminal/ # Electrobun webview│ ├── index.ts # RPC handlers + keydown dispatch + CustomEvent wiring│ ├── surface-manager.ts # Workspaces, pane layout, xterm + browser instances, chip rendering│ ├── browser-pane.ts # <electrobun-webview>, address bar, navigation, preload│ ├── pane-layout.ts # Binary tree split computation│ ├── pane-drag.ts # Drop-position overlay + commit state machine│ ├── panel-manager.ts # Sideband panel lifecycle│ ├── panel.ts # Single panel: drag, resize, render│ ├── content-renderers.ts # Extensible content renderer registry│ ├── sidebar.ts # Workspaces, status pills, port chips│ ├── process-manager.ts # ⌘⌥P overlay│ ├── settings-panel.ts # Full settings UI│ ├── command-palette.ts # ⌘⇧P fuzzy command search│ ├── terminal-effects.ts # WebGL bloom layer│ └── keyboard-shortcuts.ts # Bindings array├── web-client/ # Web mirror client│ ├── main.ts # Entry; transport + protocol + views│ ├── store.ts # Reducer-driven AppState│ ├── transport.ts # WebSocket v2 envelopes, reconnect, resume│ ├── protocol-dispatcher.ts # Server-message → store-action dispatch│ ├── sidebar.ts # Mirror sidebar render│ ├── layout.ts # Pure computeRects + applyLayout DOM pass│ └── panel-interaction.ts # Pointer/drag/resize gesture routing└── shared/ # Types shared across both processesCommon code paths
Section titled “Common code paths”Adding a settings field
Section titled “Adding a settings field”- Extend
AppSettings+DEFAULT_SETTINGS+validateSettingsinsrc/shared/settings.ts. - Add a renderer in
src/views/terminal/settings-panel.ts. - Read the new field in
SurfaceManager.applySettings(webview-only) or in theupdateSettingsRPC handler (bun-side). - Optionally add a command-palette entry in
buildPaletteCommandsto flip it without opening the panel.
Adding a CLI / socket command
Section titled “Adding a CLI / socket command”- Add the method to the matching
src/bun/rpc-handlers/<domain>.ts. It auto-merges into the dispatch table viacreateRpcHandlerinsrc/bun/rpc-handler.ts. - Add a case in
bin/ht mapCommand. - Optionally add a formatter in
formatOutputfor the human-readable form (the--jsonpath needs no extra code).
Adding a keyboard shortcut
Section titled “Adding a keyboard shortcut”Append a Binding<KeyCtx> entry to KEYBOARD_BINDINGS (or HIGH_PRIORITY_BINDINGS) in src/views/terminal/keyboard-shortcuts.ts. The id / description / category fields are picked up by the command palette automatically.
Adding a metadata field
Section titled “Adding a metadata field”See doc/system-process-metadata.md § 7 (contributor reference).
Adding a pane-bar chip
Section titled “Adding a pane-bar chip”Extend renderSurfaceChips in surface-manager.ts; add CSS in index.css. Follow the surface-chip / chip-* class conventions.
Adding a non-PTY surface kind
Section titled “Adding a non-PTY surface kind”A surface kind beyond terminal / browser / agent / telegram requires:
- Extending
PaneLeaf.surfaceTypeinsrc/shared/types.tsand the parallelsurfaceTypesrecords inWorkspaceSnapshot/PersistedWorkspace. - Adding
add<Kind>Surface/add<Kind>SurfaceAsSplit/remove<Kind>SurfaceonSurfaceManager. - Teaching
applyLayouthow to size the new kind (skipterminal.fit()). - Adding
surfType === "<kind>"to thetryRestoreLayoutbranch insrc/bun/index.tsso saved layouts re-mount instead of leaking PTY shells.
Telegram (src/views/terminal/telegram-pane.ts + src/bun/telegram-service.ts) is the smallest reference implementation.
RPC contract
Section titled “RPC contract”All three transports share the contract type TauMuxRPC in src/shared/types.ts. The Electrobun-facing handlers in src/bun/index.ts are gated by satisfies BunMessageHandlers, so any new method without a wired handler fails the typecheck — adding to the contract forces you to wire it everywhere.