| name | electron-ipc-patterns |
| description | Define and enforce IPC communication patterns between Cooper's main process, preload bridge, and renderer. Ensure all cross-process communication follows the secure, typed preload bridge pattern. |
Electron IPC Patterns
Purpose
Define and enforce the IPC communication patterns between Cooper's main process, preload bridge, and renderer. Ensure all cross-process communication follows the secure, typed preload bridge pattern.
When to Use
- Adding new IPC channels or handlers
- Modifying existing main↔renderer communication
- Creating new features that need backend capabilities
When NOT to Use
- Renderer-only changes (React state, styling)
- Changes that don't cross process boundaries
Activation Rules
Step 1: Define the IPC Contract
Every new IPC interaction requires three changes:
1. Main process handler → src/main/main.ts (or extracted module)
2. Preload bridge method → src/preload/preload.ts
3. Renderer type → src/renderer/types/ (if needed)
Step 2: Follow the Namespace Convention
Cooper uses namespaced IPC channels:
| Namespace | Purpose | Example |
|---|
copilot:* | Copilot SDK operations | copilot:send-message, copilot:create-session |
git:* | Git operations | git:create-worktree, git:get-branches |
voice:* | Voice/speech services | voice:start-listening, voice:stop |
system:* | System operations | system:open-external, system:get-platform |
mcp:* | MCP server management | mcp:list-servers, mcp:add-server |
terminal:* | PTY terminal operations | terminal:create, terminal:resize |
settings:* | Persistent settings | settings:get, settings:set |
Step 3: Implement the Pattern
Main process (src/main/main.ts):
ipcMain.handle(
'namespace:action',
async (_event, arg1: Type1, arg2: Type2): Promise<ReturnType> => {
}
);
Preload bridge (src/preload/preload.ts):
namespace: {
action: (arg1: Type1, arg2: Type2): Promise<ReturnType> =>
ipcRenderer.invoke('namespace:action', arg1, arg2),
}
Renderer usage:
const result = await window.electronAPI.namespace.action(arg1, arg2);
Step 4: Event Streams (Main → Renderer)
For streaming data (SDK events, terminal output):
mainWindow.webContents.send('copilot:event', eventData);
onCopilotEvent: (callback: (data: EventData) => void) => {
ipcRenderer.on('copilot:event', (_, data) => callback(data));
};
useEffect(() => {
window.electronAPI.copilot.onCopilotEvent((data) => {
});
}, []);
Hard Rules
- ✅ Always go through preload — Never use
ipcRenderer directly in renderer
- ✅ Always namespace channels — Use
prefix:action format
- ✅ Always type arguments — No
any types in IPC
- ✅ Always validate in main — Main process validates all inputs
- ❌ Never expose
ipcRenderer — Only expose specific methods
- ❌ Never use
remote module — Deprecated and insecure
- ❌ Never enable
nodeIntegration — Security violation
Success Criteria
- All three layers updated (main + preload + renderer)
- IPC channel follows namespace convention
- Arguments are typed, not
any
- Main process validates inputs
Related Skills