# How Electron IPC Handlers Are Registered and Structured in Lifetrace

> Discover how Lifetrace structures Electron IPC handlers using a central orchestrator and `ipcMain.handle`/`ipcMain.on` for efficient request-response and event handling.

- Repository: [FreeU-group/lifetrace](https://github.com/freeu-group/lifetrace)
- Tags: internals
- Published: 2026-03-02

---

**Lifetrace centralizes Electron IPC handler registration through a single `setupIpcHandlers` orchestrator that uses `ipcMain.handle` for async request-response channels and `ipcMain.on` for fire-and-forget events, while delegating feature-specific logic to dedicated modular files.**

All inter-process communication between the Electron main process and renderer processes in the freeu-group/lifetrace repository is organized under `free-todo-frontend/electron/`. This architecture ensures that every IPC channel is registered exactly once at startup, grouped by domain concern, and separated into manageable modules when complexity grows.

## Centralized Registration via `setupIpcHandlers`

The entire IPC system boots from one entry point. The `setupIpcHandlers` function exported from [`free-todo-frontend/electron/ipc-handlers.ts`](https://github.com/freeu-group/lifetrace/blob/main/free-todo-frontend/electron/ipc-handlers.ts) receives a `WindowManager` instance and an optional `IslandWindowManager`, then registers every channel the application requires.

```typescript
export function setupIpcHandlers(
    windowManager: WindowManager,
    islandWindowManager?: IslandWindowManager,
): void {
    // notification, window control, system...
    // delegates to specialized modules
}

```

### Registration Patterns: Handle vs. On

Inside `setupIpcHandlers`, two distinct Electron APIs are used based on communication patterns:

- **`ipcMain.handle`** – Registers a channel that returns a **Promise** to the renderer. The renderer calls `ipcRenderer.invoke` and awaits the result. Used for request-response workflows like `"show-notification"`.
- **`ipcMain.on`** – Registers an event listener that fires without returning a value to the renderer. The renderer uses `ipcRenderer.send` for fire-and-forget actions like `"set-ignore-mouse-events"`.

## Handler Organization by Domain

Channels are grouped logically within the central file to maintain readability. The implementation categorizes handlers into functional areas:

### Core System Channels

The base set includes window management and application lifecycle control:

- **Notification** – `"show-notification"` (handle)
- **Window Controls** – `"set-ignore-mouse-events"`, `"move-window"`, `"get-window-position"`, `"get-screen-info"`, `"transparent-background-ready"`, `"set-window-background-color"`
- **Lifecycle** – `"app-quit"` (on)

### Delegated Feature Modules

When logic grows complex, registration is delegated to specialized functions:

- **Todo Capture** – `setupTodoCaptureIpcHandlers(windowManager)` imported from [`ipc-handlers-todo-capture.ts`](https://github.com/freeu-group/lifetrace/blob/main/ipc-handlers-todo-capture.ts)
- **Dynamic Island UI** – `setupIslandIpcHandlers(islandWindowManager)` when the optional manager is provided

## Modular Architecture for Complex Workflows

Feature-specific IPC implementations live in separate files to prevent the main handler file from growing unmanageable.

### The Todo Capture Implementation

The todo extraction workflow resides in [`free-todo-frontend/electron/ipc-handlers-todo-capture.ts`](https://github.com/freeu-group/lifetrace/blob/main/free-todo-frontend/electron/ipc-handlers-todo-capture.ts). It exports a single registration function:

```typescript
export function setupTodoCaptureIpcHandlers(windowManager: WindowManager): void {
    ipcMain.handle("capture-and-extract-todos", async (_event, panelBounds) => {
        // capture screen, mask panel, send to backend, return result
    });
}

```

This pattern keeps [`ipc-handlers.ts`](https://github.com/freeu-group/lifetrace/blob/main/ipc-handlers.ts) focused on routing and organizational concerns while isolating domain logic in testable units.

## Renderer-to-Main Communication

Renderer processes interact with these registered channels through complementary `ipcRenderer` methods that map directly to the registration style:

```typescript
import { ipcRenderer } from "electron";

// Request-response pattern (maps to ipcMain.handle)
await ipcRenderer.invoke("show-notification", {
  id: "reminder-1",
  title: "Take a break",
  body: "Stand up and stretch",
});

// Fire-and-forget pattern (maps to ipcMain.on)
ipcRenderer.send("move-window", 100, 200);

// Async feature workflow
const result = await ipcRenderer.invoke("capture-and-extract-todos", {
  x: 10,
  y: 50,
  width: 300,
  height: 200,
});

```

## Application Startup Flow

The registration sequence executes during main process initialization:

1. **Create Managers** – Instantiate `WindowManager` (and optionally `IslandWindowManager`) after `app.whenReady()`
2. **Call Setup** – Import and execute `setupIpcHandlers(windowManager, islandManager)` from [`main.ts`](https://github.com/freeu-group/lifetrace/blob/main/main.ts)
3. **Register Channels** – Immediately register all base channels; invoke sub-handlers for todo capture and island features
4. **Launch Windows** – Create browser windows only after all IPC channels are established, ensuring renderers can communicate immediately upon load

## Summary

- **Single Entry Point** – All IPC registration flows through `setupIpcHandlers` in [`ipc-handlers.ts`](https://github.com/freeu-group/lifetrace/blob/main/ipc-handlers.ts)
- **Dual Pattern System** – Use `ipcMain.handle` for async operations requiring responses and `ipcMain.on` for one-way events
- **Domain Grouping** – Channels are organized by function: notifications, window controls, and lifecycle events
- **Modular Delegation** – Complex features like todo capture extract their handlers into separate files (e.g., [`ipc-handlers-todo-capture.ts`](https://github.com/freeu-group/lifetrace/blob/main/ipc-handlers-todo-capture.ts)) to maintain clean architecture
- **Type Safety** – Handlers receive strongly typed `WindowManager` and optional `IslandWindowManager` dependencies rather than accessing global window objects

## Frequently Asked Questions

### What is the difference between `ipcMain.handle` and `ipcMain.on` in Lifetrace?

Lifetrace uses `ipcMain.handle` for channels that must return data to the renderer, such as `"capture-and-extract-todos"` which processes screen captures and returns extracted tasks. It uses `ipcMain.on` for fire-and-forget operations like `"app-quit"` or `"move-window"` where the renderer only needs to trigger an action without waiting for a result.

### How does Lifetrace structure large IPC feature sets?

The codebase extracts complex workflows into dedicated files like [`ipc-handlers-todo-capture.ts`](https://github.com/freeu-group/lifetrace/blob/main/ipc-handlers-todo-capture.ts). These modules export setup functions (e.g., `setupTodoCaptureIpcHandlers`) that accept the `WindowManager` and register their specific channels. The main `setupIpcHandlers` function calls these sub-functions, keeping the central file readable while isolating domain logic.

### Where does the main process call the IPC setup function?

The main process entry point (typically [`main.ts`](https://github.com/freeu-group/lifetrace/blob/main/main.ts)) calls `setupIpcHandlers` immediately after creating the `WindowManager` instance during the `app.whenReady()` lifecycle event. This ensures all channels are registered before any browser windows are created, preventing race conditions where renderers might attempt to communicate before handlers exist.

### How do renderer processes discover available IPC channels in this architecture?

Renderer code imports `ipcRenderer` from Electron and uses `invoke` or `send` with string channel names that match those registered in the main process. For example, `ipcRenderer.invoke("show-notification", payload)` calls the handler registered via `ipcMain.handle("show-notification", ...)` in [`free-todo-frontend/electron/ipc-handlers.ts`](https://github.com/freeu-group/lifetrace/blob/main/free-todo-frontend/electron/ipc-handlers.ts).