Skip to main content

IPC Bridge Communication

Communication between the React frontend and the C# host runs over Edge WebView2 using two distinct, one-directional message shapes: Actions (UI → host) and Event envelopes (host → UI). The frontend seam lives in frontend/src/infrastructure/bridge.ts.

Sending Actions (UI → host)​

The UI posts an { action, payload } message. The C# ActionHost validates and routes it — reads run concurrently, while mutations are serialized one at a time.

export function sendAction(action: string, payload?: unknown): void {
window.chrome?.webview?.postMessage({ action, payload });
}

Receiving Events (host → UI)​

The host emits { event, payload } envelopes with PostWebMessageAsJson. The bridge attaches a single message listener, then validates each envelope with a Zod schema before dispatching it to subscribers — an invalid payload is rejected instead of reaching React state.

webview.addEventListener('message', (e) => {
const envelope = IpcEventEnvelopeSchema.parse(e.data); // { event, payload }
dispatchHostEvent(envelope);
});

Canonical Event names include logReceived, statusUpdated, progressUpdated, backupsLoaded, tasksLoaded, restorePointsLoaded, settingsLoaded, updateAvailable, and actionFinished.

The action lifecycle​

Every mutation acquires an execution lock the moment its Action is sent. The lock is released authoritatively by the matching actionFinished Event, which the host emits from a finally block — so the UI unlocks reliably even when the command fails. Reads never take the lock. See useExecutionLifecycle.ts for the hook that owns this.

// desktop/Ipc/ActionHost.cs — emitted in a finally, ok reflects real success
_events.Emit(IpcEvents.ActionFinished, new { action, ok });