Architecture
How the Electron app bundles the gateway and Next.js frontend into a native experience.
Desktop Architecture
The Reeve Desktop App is an Electron shell that manages two child processes: the Reeve gateway (Node.js) and the Cockpit frontend (Next.js standalone).
Process Model
Electron Main Process (src/main/index.js)
│
├── GatewayProcess (src/main/gateway.js)
│ └── node reeve-gateway/ --port 18789
│
├── FrontendProcess (src/main/frontend.js)
│ └── node frontend/.next/standalone/server.js --port 3100
│
├── BrowserWindow
│ └── http://localhost:3100/cockpit
│
├── Tray (menubar icon)
│
└── AutoUpdater (src/main/updater.js)
└── GitHub Releases checkMain Process (index.js)
The entry point that orchestrates everything:
// Startup sequence
app.whenReady().then(async () => {
// 1. Start gateway child process
gateway = new GatewayProcess(getGatewayPath());
await gateway.start(GATEWAY_PORT);
// 2. Start frontend child process
frontend = new FrontendProcess(getFrontendPath());
await frontend.start(FRONTEND_PORT);
// 3. Create main window pointing at frontend
createMainWindow();
// 4. Set up tray icon
createTray();
// 5. Check for updates
setupAutoUpdater();
});Gateway Process (gateway.js)
Manages the Reeve gateway as a child process:
class GatewayProcess {
constructor(gatewayPath) {
this.path = gatewayPath;
this.process = null;
}
async start(port) {
this.process = spawn("node", ["dist/cli/index.js", "start", "--port", port], {
cwd: this.path,
env: {
...process.env,
REEVE_DESKTOP: "true",
PORT: String(port),
},
});
// Wait for gateway to be ready (health check)
await this.waitForReady(port);
}
stop() {
this.process?.kill("SIGTERM");
}
}Frontend Process (frontend.js)
Runs the Next.js standalone server:
class FrontendProcess {
constructor(frontendPath) {
this.path = frontendPath;
}
async start(port) {
this.process = spawn("node", [".next/standalone/server.js"], {
cwd: this.path,
env: {
...process.env,
PORT: String(port),
NEXT_PUBLIC_GATEWAY_URL: `http://localhost:${GATEWAY_PORT}`,
},
});
}
}Build System
Packaging
The electron-builder config packages everything:
{
"build": {
"appId": "com.mindfortress.reeve",
"extraResources": [
{ "from": "gateway/", "to": "gateway" },
{ "from": "frontend/", "to": "frontend" }
]
}
}Pack Scripts
Before building, the gateway and frontend are packed:
# Pack gateway: copies dist/ from reeve repo
npm run pack:gateway
# Pack frontend: builds Next.js standalone output
npm run pack:frontend
# Build the .dmg
npm run buildThe pack scripts (scripts/pack-gateway.js, scripts/pack-frontend.js) copy the built artifacts into the desktop app's resource directories.
Paths
| Context | Gateway Path | Frontend Path |
|---|---|---|
| Dev mode | ../../gateway/ (local repo) | ../../frontend/ (dev server) |
| Production | resources/gateway/ (bundled) | resources/frontend/ (bundled) |
In dev mode (--dev flag), the gateway points at the local repo and the frontend uses the Next.js dev server on port 3000.
Auto-Updater
Uses electron-updater with GitHub Releases:
function setupAutoUpdater() {
autoUpdater.setFeedURL({
provider: "github",
owner: "MindFortressInc",
repo: "reeve-desktop"
});
autoUpdater.checkForUpdatesAndNotify();
autoUpdater.on("update-available", (info) => {
dialog.showMessageBox({
message: `Update available: v${info.version}`,
buttons: ["Update", "Later"]
});
});
}The desktop app uses hardened runtime and notarization for macOS. The electron-builder config handles code signing automatically when CSC_LINK and CSC_KEY_PASSWORD environment variables are set.
Window Management
- Single window — One main BrowserWindow showing the Cockpit
- Window controls — Native macOS title bar, traffic lights
- Minimum size — 800×600
- Restore state — Window position and size saved between sessions
- Deep links —
reeve://protocol handler for auth callbacks