ADR-0015: Classic Electron IPC vs Guest↔Host RPC — when to use which
Status: Active · Date: 2026-07-23
References
- ADR-0003: Cap’n Web RPC — the Guest↔Host RPC layer this decision draws a boundary around
- Guest↔Host RPC — the living document for that layer (transport, trust model, capabilities)
- First applied by the New Default Window command (
tranquil-config→atom-application.js)
Context
Tranquil now has two ways to cross a process/document boundary, and new features were starting to guess which to use:
- Classic Electron IPC — named string channels. Host renderer ↔ main process via
ipcRenderer/ipcMain(wrapped byipc-helpers); host renderer ↔ browser<webview>guest via the preload bridge (window.electron.sendToHost/receive). Structured-clone payloads; functions cannot cross. - Guest↔Host RPC — the
tranquil-rpcobject-capability layer over Cap’n Web (ADR-0003). Functions/objects pass by reference as stubs; the host hands a trusted guest page ahostcapability object (window.tranquilHost). Runs only in trustedfile://webview pages under a registered root; untrusted origins get nothing (default-deny).
The trigger was “File → New Default Window”: a tranquil-config renderer command that
asks the main process to seed and open a window. Which mechanism? The answer needs to be a
rule, not a per-feature judgement call.
Decision
- Use classic Electron IPC for first-party host↔main work — application and window
lifecycle, commands, config. New Default Window sends
tranquil:new-default-windowoveripcRenderer.send→ipcHelpers.on(ipcMain, …), exactly like the existingapplication:new-window-open-urlcommand. - Use Guest↔Host RPC only for browser
<webview>guest pages (trustedfile://content, e.g. the business-theme mockups) that need host capabilities across the webview boundary —notify,paneControls.register, etc. Untrusted web content is never given the runtime.
Window and application lifecycle are main-process concerns and are never guest capabilities, so they stay on classic IPC. The RPC layer operates on the renderer↔webview boundary; it does not (and should not) reach the main process.
Consequences
- A clear, mechanical rule: who are the two parties? Host renderer ↔ main → classic IPC. Host renderer ↔ trusted webview guest → Guest↔Host RPC. Untrusted web content → no host surface at all.
- We do not route everything through the RPC layer. It is renderer↔webview only, and it carries trust-classification and per-session machinery that is irrelevant (and unavailable) on the host↔main path.
- This refines ADR-0003 rather than changing it — ADR-0003 introduced the capability layer; this ADR fixes the boundary between it and classic IPC so the two don’t get conflated.