Electron Setup
Architecture
Section titled “Architecture”The xNet Electron app splits work between two processes:
┌─────────────────────┐ IPC / MessagePort ┌────────────────────────┐│ Main Process │◄─────────────────────────►│ Renderer Process ││ │ │ ││ BSM (sync manager) │ │ React app + hooks ││ ProcessManager │ │ TipTap editor ││ LocalAPIServer │ │ IPCSyncManager ││ MCPServer │ │ ServiceClient ││ WebSocket client │ │ DevTools panels │└─────────────────────┘ └────────────────────────┘Main process — Runs the Background Sync Manager, manages child processes, hosts the Local API and MCP servers. Maintains its own Y.Doc pool and WebSocket connection to the signaling server.
Renderer process — Runs the React UI. Communicates with the main process via IPC for sync operations. Maintains mirror Y.Docs for editor binding.
Why this split?
Section titled “Why this split?”Sync is CPU-intensive (BLAKE3 hashing, Ed25519 signing, Yjs encoding). Running it in the main process keeps the renderer responsive. The BSM also survives renderer crashes and can sync in the background when no window is open.
Running the dev server
Section titled “Running the dev server”cd apps/electron && pnpm devThis starts:
- The Vite dev server on
http://localhost:5177 - The signaling server on port
4444 - The Electron app
Two-instance sync testing
Section titled “Two-instance sync testing”cd apps/electron && pnpm dev:bothThis launches two Electron windows connected to the same signaling server. Edits in one window appear in the other in real time.
Background Sync Manager (BSM)
Section titled “Background Sync Manager (BSM)”The BSM (apps/electron/src/main/bsm.ts) is the Electron-specific sync engine. It runs independently of the renderer lifecycle.
IPC channels
Section titled “IPC channels”| Channel | Direction | Purpose |
|---|---|---|
xnet:bsm:start | Renderer → Main | Connect to signaling URL, set signing credentials |
xnet:bsm:acquire | Renderer → Main | Get a Y.Doc for editing; returns a MessagePort |
xnet:bsm:release | Renderer → Main | Done editing a document |
xnet:bsm:track | Renderer → Main | Add node to background sync set |
xnet:bsm:untrack | Renderer → Main | Remove from background sync |
xnet:bsm:status | Renderer → Main | Pool/tracked/queue stats |
Document flow
Section titled “Document flow”When a component calls useNode(nodeId):
- Renderer calls
window.xnetBSM.acquire(nodeId)via IPC - BSM creates a Y.Doc, joins the sync room, creates a
MessageChannelMain - Renderer gets the
MessagePortand creates a local mirror Y.Doc - Edits flow through the MessagePort as binary Yjs updates (zero-copy transfer)
- BSM signs updates with
signYjsUpdate()and broadcasts via WebSocket - Remote updates are verified (
verifyYjsEnvelope), applied to BSM doc, forwarded to renderer
Identity setup
Section titled “Identity setup”The renderer passes signing credentials to the BSM on startup:
// In the rendererconst syncManager = new IPCSyncManager()await syncManager.start(signalingUrl)syncManager.setIdentity(authorDID, signingKey)The BSM uses these credentials to sign all outgoing Yjs envelopes.
IPCSyncManager
Section titled “IPCSyncManager”The renderer-side IPCSyncManager implements the same SyncManager interface as the web version, but routes everything through IPC:
interface SyncManager { start(): Promise<void> stop(): Promise<void> acquire(nodeId): Promise<Y.Doc> release(nodeId): void track(nodeId, schemaId): void untrack(nodeId): void getAwareness(nodeId): Awareness | null readonly status: ConnectionStatus readonly poolSize: number readonly trackedCount: number readonly queueSize: number}The API is identical whether you’re building for Electron or Web. React hooks and components work the same way — the sync manager is swapped at the provider level.
Plugin services
Section titled “Plugin services”Electron enables Layer 3 (Services) and Layer 4 (Integrations) of the plugin system:
ProcessManager
Section titled “ProcessManager”Runs in the main process. Manages child processes with health checks and restart policies:
// From a plugin's activate() functionconst services = createServiceClient()await services.start({ id: 'my-analysis-service', name: 'Data Analysis', process: { command: 'python', args: ['server.py'] }, lifecycle: { restart: 'on-failure', maxRestarts: 3 }, communication: { protocol: 'http', port: 8080 }})
const result = await services.call('my-analysis-service', 'POST', '/analyze', data)Local API
Section titled “Local API”REST API on localhost:31415 for external tools (Raycast, Alfred, shell scripts, etc.):
curl http://localhost:31415/api/v1/nodes?schema=xnet://app/Taskcurl -X POST http://localhost:31415/api/v1/nodes \ -H "Content-Type: application/json" \ -d '{"schema": "xnet://app/Task", "properties": {"title": "New task"}}'MCP server
Section titled “MCP server”Exposes xNet data to AI agents via stdin/stdout JSON-RPC:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | xnet-mcpSee the Plugin Development guide for full details on services and integrations.
DevTools
Section titled “DevTools”The Electron app includes 7 debug panels accessible from the app:
- Sync — Connection status, pool state, peer scoring
- Store — NodeStore contents, change history
- Schema — Registered schemas and property types
- Identity — Current DID, key info
- Network — WebSocket messages, peer connections
- Plugins — Registered plugins, contributions, middleware
- Performance — Render timing, sync latency
Enable verbose sync logging:
localStorage.setItem('xnet:sync:debug', 'true')Experimental spatial Library viewer
Section titled “Experimental spatial Library viewer”The Library graph’s Spatial viewer control shares the saved links in the current view with a separate, read-only browser viewer. Search and source/group filters remain available in its flat view. A supported headset can request a stationary WebXR overview with a selected-resource card, Home, Back and explicit travel.
To connect a headset, put it and the Mac on a trusted private network. Provide the Mac’s HTTPS address with a port above 1023, then select a valid certificate for that address and its private key. Establish certificate trust on both devices through their operating systems; do not bypass browser warnings. The key stays on the Mac. Open the copied pairing link within five minutes. It can be used once, expires within one hour, and loses its capability on reload. Revoke access or quitting the desktop app stops access. Previously viewed content cannot be recalled.
The viewer exposes scoped graph data, saved details and thumbnails. It does not expose private conversation records, the development debugger or the general Local API. A preview or written caption remains distinct from a spoken transcript.
Vision Pro and PlayStation VR2 Sense support, headset performance and comfort are not yet physically verified. Experimental flight requires observed tracked position, rotation and analog trigger range, explicit arming and trigger release. Full-pose flight is not enabled. Choose Exit / search to leave immersion and use normal browser text input; an immersive DOM overlay is not assumed.
Developers can run the synthetic 1,000, 10,000 and 56,000-link fixtures with
pnpm --filter xnet-desktop spatial:dev. The development server is loopback-only;
use the paired HTTPS path for a headset. The device review procedure is in
apps/electron/src/spatial/README.md in the repository.
Building for production
Section titled “Building for production”cd apps/electron && pnpm buildThis produces platform-specific binaries. The signaling server URL and other configuration are set via environment variables or the app’s settings panel.