# AGENTS.md - Desktop App Guidelines Thin Electrobun shell hosting the fullstack server app. ## Tech Stack > **⚠️ This project uses Bun — NOT Node.js / npm. All commands use `bun`. Never use `npm`, `npx`, or `node`.** - **Type**: Electrobun desktop application - **Design**: Server-driven desktop (thin native shell hosting web app) - **Runtime**: Bun (Main process) + CEF (Chromium Embedded Framework) - **Framework**: Electrobun - **Build**: Electrobun CLI + Turborepo ## Architecture - **Server-driven design**: The desktop app is a "thin" native shell. It does not contain UI or business logic; it merely hosts the `apps/server` TanStack Start application in a native window. - **Dev mode**: Connects to an external Vite dev server at `localhost:3000`. Requires `apps/server` to be running separately. - **Prod mode**: Spawns an embedded TanStack Start server (Nitro) as a child process and loads the dynamically assigned local URL. - **Config**: `electrobun.config.ts` manages app metadata (identifier, name), build entries, and asset bundling. ## Commands ```bash # Development bun dev # Start Electrobun dev mode (requires server dev running) # Build bun build # Build stable release (all platforms) # Code Quality bun fix # Biome auto-fix bun typecheck # TypeScript check ``` ## Directory Structure ``` . ├── src/ │ └── bun/ │ └── index.ts # Main process entry (Window management + server lifecycle) ├── electrobun.config.ts # App metadata and build/copy configuration ├── package.json # Scripts and dependencies ├── turbo.json # Build pipeline dependencies (depends on server build) └── AGENTS.md # Desktop guidelines (this file) ``` ## Development Workflow 1. **Start server**: In `apps/server`, run `bun dev`. 2. **Start desktop**: In `apps/desktop`, run `bun dev`. 3. **Connection**: The desktop app polls `localhost:3000` until responsive, then opens the native window. ## Production Architecture ### Build Pipeline The desktop build is orchestrated by Turbo. It depends on the server's production build: - `turbo.json`: `@furtherverse/desktop#build` depends on `@furtherverse/server#build`. - `electrobun.config.ts`: Copies `../server/.output` to `server-output` folder within the app bundle. ### Server Lifecycle In production, the main process manages the embedded server: - **Spawn**: Spawns server from `server-output/server/index.mjs` using `Bun.spawn`. - **Port Allocation**: A free port is pre-allocated via `node:net` (`createServer` on `127.0.0.1:0`), then passed to the server as the `PORT` environment variable. - **Readiness Check**: The main process polls the server URL with `fetch` until it responds, rather than parsing stdout. - **Retry**: If the server fails to become ready (timeout or early exit), the process is killed and a new attempt is made with a fresh port (up to 3 retries). - **Lifecycle**: The server process is tied to the app; it is killed on `SIGTERM`, `SIGINT`, or app exit. If the server process crashes, the app exits with an error. ## Environment Detection The application determines its environment via the `ELECTROBUN_BUILD_ENV` variable, automatically set by the Electrobun CLI: ```typescript const isDev = () => { const env = process.env.ELECTROBUN_BUILD_ENV return !env || env === 'dev' } ``` - **dev**: Development mode (connects to external host). - **canary / stable**: Production mode (starts embedded server). ## Environment Variables - `ELECTROBUN_BUILD_ENV`: Auto-set by CLI. Determines whether to use local dev server or embedded server. - `DATABASE_URL`: Required by the server process. Must be passed through from the parent environment to the spawned child process. ## Electrobun Patterns ### BrowserWindow Configuration The main window uses the CEF renderer for consistency across platforms. ```typescript new BrowserWindow({ title: 'Furtherverse', url: serverUrl, frame: { x: 100, y: 100, width: 1200, height: 800, }, renderer: 'cef', }) ``` ### Path Aliases The main process uses `electrobun/bun` for native APIs and `PATHS` for locating bundled assets. ```typescript import { BrowserWindow, PATHS } from 'electrobun/bun' // Locate the embedded server bundle const serverEntryPath = join(PATHS.VIEWS_FOLDER, '..', 'server-output', 'server', 'index.mjs') ``` ## Critical Rules **DO:** - Use arrow functions for all components and utility functions. - Ensure `apps/server` is built before building `apps/desktop` (handled by Turbo). - Pre-allocate a free port and pass it via `PORT` env var instead of parsing stdout. - Handle server process termination to avoid orphan processes. - Use `catalog:` for all dependency versions in `package.json`. **DON'T:** - Use `npm`, `npx`, `node`, `yarn`, or `pnpm`. Always use `bun`. - Hardcode `localhost:3000` for production builds. - Include UI components or business logic in the desktop app (keep it thin). - Use `as any` or `@ts-ignore`.