Reeve
Desktop App

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 check

Main 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 build

The pack scripts (scripts/pack-gateway.js, scripts/pack-frontend.js) copy the built artifacts into the desktop app's resource directories.

Paths

ContextGateway PathFrontend Path
Dev mode../../gateway/ (local repo)../../frontend/ (dev server)
Productionresources/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 linksreeve:// protocol handler for auth callbacks

On this page