diff --git a/.sisyphus/plans/electrobun-prod-mode.md b/.sisyphus/plans/electrobun-prod-mode.md new file mode 100644 index 0000000..bfe4422 --- /dev/null +++ b/.sisyphus/plans/electrobun-prod-mode.md @@ -0,0 +1,798 @@ +# Electrobun Desktop: Production Mode via Child Process Architecture + +## TL;DR + +> **Quick Summary**: Redesign the Electrobun desktop app to support production mode by spawning the TanStack Start server as a child process. Currently only dev mode works (hardcoded `localhost:3000`). The desktop will detect dev/prod mode, spawn the server with `PORT=0` in prod, parse the actual port from stdout, and open the BrowserWindow. +> +> **Deliverables**: +> - Rewritten `apps/desktop/src/bun/index.ts` with dev/prod mode support +> - Updated `apps/desktop/electrobun.config.ts` with `build.copy` and platform configs +> - Cross-workspace build dependency in turbo pipeline +> - Updated `apps/desktop/AGENTS.md` reflecting new architecture +> +> **Estimated Effort**: Medium +> **Parallel Execution**: YES - 2 waves +> **Critical Path**: Tasks 1,2,3 (parallel) → Task 4 → Task 5 + +--- + +## Context + +### Original Request +Redesign the Electrobun desktop app to support production mode. The desktop app should spawn the TanStack Start server as a child process, detect dev vs prod mode at runtime, use system-assigned ports for security, and handle server lifecycle (crash, quit). + +### Confirmed Decisions +- **Architecture**: Desktop spawns server as child process via `Bun.spawn` +- **Server artifact**: `.output/server/index.mjs` (not compiled binary) — Electrobun already bundles Bun +- **Port strategy**: `PORT=0` (system-assigned), `HOST=127.0.0.1` +- **Dev/Prod detection**: `process.env.ELECTROBUN_BUILD_ENV` (see Defaults Applied below) +- **Target platforms**: All (Linux, macOS, Windows) +- **DATABASE_URL**: Pass-through via env var, no special handling +- **Crash handling**: MVP — log error to stderr, exit process + +### Research Findings + +**Electrobun APIs (verified from source code):** +- `build.copy` supports paths outside the project directory (e.g., `../server/.output`). Source paths are resolved relative to the project root. Destinations map into `Resources/app/` in the bundle. +- `PATHS` exported from `electrobun/bun` provides `RESOURCES_FOLDER` (absolute path to `Resources/`) and `VIEWS_FOLDER` (`Resources/app/views/`). +- `process.execPath` in a bundled Electrobun app points to the bundled Bun binary. +- `Electrobun.events.on('before-quit', callback)` fires before app quit. Callback receives an event with `response({ allow: false })` to cancel quit. +- `ELECTROBUN_BUILD_ENV` is set by the Electrobun CLI: `"dev"` for `electrobun dev`, `"stable"` for `electrobun build --env=stable`. + +**Server startup behavior (verified from built output):** +- `.output/server/index.mjs` uses `Bun.serve` via the `h3+srvx` adapter (Nitro bun preset). +- Startup log format: `➜ Listening on: http://
:/` +- The log uses the actual assigned address/port, not the requested one. So `PORT=0` will log the real port. +- `DATABASE_URL` is validated at startup via Zod (`@t3-oss/env-core`). Missing = immediate crash. +- The `.output/server/` directory contains `index.mjs` plus `_libs/` with bundled dependencies. + +**Turbo pipeline:** +- Root `turbo.json` has `build.dependsOn: ["^build"]` which only builds workspace *dependencies*. +- Desktop does NOT depend on server in `package.json`, so `^build` won't trigger server build. +- Need explicit cross-workspace dependency via desktop's `turbo.json`. + +### Metis Review +**Identified Gaps** (addressed): +- Dev/prod detection mechanism: Switched from custom `ELECTROBUN_DEV` to built-in `ELECTROBUN_BUILD_ENV` +- Server startup timeout: Added explicit timeout with error reporting +- Port parsing failure: Plan includes fallback and error handling +- Server crash during runtime: Watching `subprocess.exited` promise +- `cwd` for spawned server: Must set to server directory for relative import resolution +- Cross-platform considerations: `ELECTROBUN_BUILD_ENV` works everywhere (no `cross-env` needed) + +### Unknowns Resolved + +| Unknown | Resolution | +|---------|------------| +| Does `build.copy` support paths outside project? | **YES** — uses `cpSync` with source resolved from project root. `../server/.output` works. | +| Runtime API for resolving bundled resource paths? | **`PATHS.RESOURCES_FOLDER`** from `electrobun/bun`. Copied files land in `Resources/app/{dest}/`. | +| Does Nitro log actual port with PORT=0? | **YES** — format: `➜ Listening on: http://:/` via h3+srvx adapter. | +| How does Electrobun detect dev mode? | **`ELECTROBUN_BUILD_ENV`** env var set by CLI. Values: `dev`, `canary`, `stable`. | + +--- + +## Work Objectives + +### Core Objective +Enable the Electrobun desktop app to run in production mode by spawning the TanStack Start server as a managed child process, while preserving existing dev mode behavior. + +### Concrete Deliverables +- `apps/desktop/src/bun/index.ts` — Complete rewrite with dual-mode support +- `apps/desktop/electrobun.config.ts` — `build.copy` + all-platform configs +- `apps/desktop/turbo.json` — Cross-workspace build dependency +- `apps/desktop/AGENTS.md` — Accurate documentation of new architecture + +### Definition of Done +- [ ] `bun typecheck` passes from monorepo root (zero errors) +- [ ] `bun build` from root succeeds: server builds first, then desktop bundles server output +- [ ] `bun dev` from root still starts both apps (dev mode preserved) +- [ ] Desktop `index.ts` has zero hardcoded URLs (all dynamic) + +### Must Have +- Dev mode: poll external `localhost:3000`, open window when ready (existing behavior, refactored) +- Prod mode: spawn server via `Bun.spawn`, parse port from stdout, open window +- Server bound to `127.0.0.1` only (no network exposure) +- `PORT=0` for system-assigned port (no conflicts) +- Server process killed on app quit (via `before-quit` event) +- Server crash detection (watch `exited` promise, log error, exit app) +- Startup timeout with clear error message +- Server `cwd` set to its own directory (for relative import resolution) +- `DATABASE_URL` passed through from parent environment + +### Must NOT Have (Guardrails) +- No hardcoded port numbers (not even 3000 — use a named constant `DEV_SERVER_URL`) +- No `as any`, `@ts-ignore`, or `@ts-expect-error` +- No empty catch blocks — always handle or re-throw +- No `npm`, `npx`, `node` — Bun only +- No manual `useMemo`/`useCallback` (not relevant here, but per project rules) +- No suppressed type errors — fix them properly +- No custom env var for dev detection — use built-in `ELECTROBUN_BUILD_ENV` +- No compiled server binary — use `.output/server/index.mjs` with bundled Bun +- Do NOT edit `apps/server/` — only `apps/desktop/` files change +- Do NOT add `@furtherverse/server` as a package dependency of desktop (use turbo cross-workspace dependency instead) + +--- + +## Verification Strategy + +> **UNIVERSAL RULE: ZERO HUMAN INTERVENTION** +> +> ALL tasks in this plan MUST be verifiable WITHOUT any human action. +> Every criterion is verified by running a command or using a tool. + +### Test Decision +- **Infrastructure exists**: NO (no test framework in this project) +- **Automated tests**: NO (per project state — `AGENTS.md` says "No test framework configured yet") +- **Framework**: None +- **Agent-Executed QA**: ALWAYS (mandatory for all tasks) + +--- + +## Execution Strategy + +### Parallel Execution Waves + +``` +Wave 1 (Start Immediately — all independent, different files): +├── Task 1: Update electrobun.config.ts (build.copy + platform configs) +├── Task 2: Update turbo.json (cross-workspace build dependency) +└── Task 3: Rewrite index.ts (complete dev/prod mode implementation) + +Wave 2 (After Wave 1 — needs final state of all files): +├── Task 4: Rewrite AGENTS.md (documentation reflecting new architecture) +└── Task 5: End-to-end verification (typecheck + build pipeline) +``` + +### Dependency Matrix + +| Task | Depends On | Blocks | Can Parallelize With | +|------|------------|--------|---------------------| +| 1 | None | 4, 5 | 2, 3 | +| 2 | None | 4, 5 | 1, 3 | +| 3 | None | 4, 5 | 1, 2 | +| 4 | 1, 2, 3 | 5 | None | +| 5 | 1, 2, 3, 4 | None | None (final) | + +### Agent Dispatch Summary + +| Wave | Tasks | Recommended Agents | +|------|-------|-------------------| +| 1 | 1, 2, 3 | Tasks 1,2: `delegate_task(category="quick")`. Task 3: `delegate_task(category="unspecified-high")` | +| 2 | 4, 5 | Task 4: `delegate_task(category="writing")`. Task 5: `delegate_task(category="quick")` | + +--- + +## TODOs + +- [ ] 1. Update `apps/desktop/electrobun.config.ts` — Add build.copy and platform configs + + **What to do**: + - Add `build.copy` to include the server's Nitro build output in the desktop bundle: + ```typescript + copy: { + '../server/.output': 'server-output', + } + ``` + This copies `apps/server/.output/` (the entire Nitro build output) into `Resources/app/server-output/` in the Electrobun bundle. The full server entry point will be at `Resources/app/server-output/server/index.mjs`. + - Add `macOS` platform config block (currently only `linux` exists): + ```typescript + macOS: { + bundleCEF: true, + } + ``` + - Add `windows` platform config block: + ```typescript + windows: { + bundleCEF: true, + } + ``` + - Verify the exact property names by checking Electrobun's `ElectrobunConfig` type definition. The `linux` block already uses `bundleCEF: true`, so follow the same pattern for other platforms. If the type doesn't support `macOS`/`windows` yet, skip those and leave a `// TODO:` comment explaining what's needed. + - Preserve existing config values exactly (app name, identifier, version, bun entrypoint, linux config). + + **Must NOT do**: + - Do not change the app name, identifier, or version + - Do not change the bun entrypoint path + - Do not remove the existing `linux` config + - Do not add dependencies or scripts + + **Recommended Agent Profile**: + - **Category**: `quick` + - Reason: Single file, small config change, clear specification + - **Skills**: `[]` + - No specialized skills needed — straightforward TypeScript config edit + - **Skills Evaluated but Omitted**: + - `frontend-ui-ux`: No UI work involved + + **Parallelization**: + - **Can Run In Parallel**: YES + - **Parallel Group**: Wave 1 (with Tasks 2, 3) + - **Blocks**: Tasks 4, 5 + - **Blocked By**: None (can start immediately) + + **References**: + + **Pattern References** (existing code to follow): + - `apps/desktop/electrobun.config.ts` — Current config structure. The `linux.bundleCEF: true` pattern should be replicated for other platforms. The `build.bun.entrypoint` key shows where build config lives. + + **API/Type References** (contracts to implement against): + - The `ElectrobunConfig` type from `electrobun` — imported via `import type { ElectrobunConfig } from 'electrobun'`. Check its definition (likely in `node_modules/electrobun/`) to verify exact property names for `copy`, `macOS`, `windows`. + + **External References**: + - Electrobun `build.copy` syntax: copies source (relative to project root) into `Resources/app/{dest}/` in the bundle. Uses `cpSync` with `dereference: true`. + + **WHY Each Reference Matters**: + - `electrobun.config.ts`: You're editing this file — need to know its current shape to preserve existing values + - `ElectrobunConfig` type: Must match the type definition exactly — don't guess property names + + **Acceptance Criteria**: + - [ ] `build.copy` key exists with `'../server/.output': 'server-output'` mapping + - [ ] Platform configs added for all three platforms (or TODO comments if types don't support them) + - [ ] Existing config values unchanged (app.name = 'Desktop', etc.) + - [ ] File passes `bun typecheck` (no type errors) + + **Agent-Executed QA Scenarios:** + + ``` + Scenario: Config file is valid TypeScript with correct types + Tool: Bash + Preconditions: None + Steps: + 1. Run: bun typecheck (from apps/desktop/) + 2. Assert: Exit code 0 + 3. Assert: No errors mentioning electrobun.config.ts + Expected Result: TypeScript compilation succeeds + Evidence: Terminal output captured + + Scenario: build.copy key has correct structure + Tool: Bash (grep) + Preconditions: File has been edited + Steps: + 1. Read apps/desktop/electrobun.config.ts + 2. Assert: Contains '../server/.output' + 3. Assert: Contains 'server-output' + 4. Assert: File still contains 'satisfies ElectrobunConfig' + Expected Result: Config has copy mapping and type annotation + Evidence: File contents + ``` + + **Commit**: YES (groups with 2) + - Message: `feat(desktop): add build.copy for server bundle and platform configs` + - Files: `apps/desktop/electrobun.config.ts` + - Pre-commit: `bun typecheck` (from `apps/desktop/`) + +--- + +- [ ] 2. Update `apps/desktop/turbo.json` — Add cross-workspace build dependency + + **What to do**: + - Add `dependsOn` to the existing `build` task to ensure the server builds before the desktop: + ```json + { + "tasks": { + "build": { + "dependsOn": ["@furtherverse/server#build"], + "outputs": ["build/**", "artifacts/**"] + } + } + } + ``` + - This tells Turbo: "before running `build` for `@furtherverse/desktop`, first run `build` for `@furtherverse/server`." + - This ensures `apps/server/.output/` exists when `electrobun build` runs and tries to `build.copy` from `../server/.output`. + - Preserve the existing `outputs` array exactly. + + **Must NOT do**: + - Do not modify the root `turbo.json` — only `apps/desktop/turbo.json` + - Do not remove existing `outputs` + - Do not add other tasks or change other config + + **Recommended Agent Profile**: + - **Category**: `quick` + - Reason: Single file, one-line JSON change + - **Skills**: `[]` + - No specialized skills needed + - **Skills Evaluated but Omitted**: + - `git-master`: Commit will be grouped with Task 1 + + **Parallelization**: + - **Can Run In Parallel**: YES + - **Parallel Group**: Wave 1 (with Tasks 1, 3) + - **Blocks**: Tasks 4, 5 + - **Blocked By**: None (can start immediately) + + **References**: + + **Pattern References** (existing code to follow): + - `apps/desktop/turbo.json` — Current file with `build.outputs` already defined. You're adding `dependsOn` alongside it. + - `turbo.json` (root) — Shows existing turbo patterns like `build.dependsOn: ["^build"]`. The root already uses `^build` for workspace dependencies, but since desktop doesn't list server as a package dependency, we need an explicit cross-workspace reference. + + **API/Type References**: + - Turbo `dependsOn` syntax: `"@furtherverse/server#build"` means "run the `build` task in the `@furtherverse/server` workspace". + + **Documentation References**: + - `apps/server/package.json` — The package name is `@furtherverse/server` (verify this is the exact name used in the `dependsOn` reference). + + **WHY Each Reference Matters**: + - `apps/desktop/turbo.json`: You're editing this file — preserve existing outputs + - `apps/server/package.json`: Need exact package name for the cross-workspace reference + - Root `turbo.json`: Context for existing turbo patterns in this project + + **Acceptance Criteria**: + - [ ] `apps/desktop/turbo.json` has `dependsOn: ["@furtherverse/server#build"]` in the build task + - [ ] Existing `outputs` array is preserved + - [ ] Valid JSON (no syntax errors) + + **Agent-Executed QA Scenarios:** + + ``` + Scenario: turbo.json is valid JSON with correct structure + Tool: Bash + Preconditions: File has been edited + Steps: + 1. Run: bun -e "JSON.parse(require('fs').readFileSync('apps/desktop/turbo.json', 'utf8'))" + 2. Assert: Exit code 0 (valid JSON) + 3. Read the file and verify structure contains both dependsOn and outputs + Expected Result: Valid JSON with both keys present + Evidence: Terminal output captured + + Scenario: Turbo resolves the cross-workspace dependency + Tool: Bash + Preconditions: turbo.json updated + Steps: + 1. Run: bunx turbo build --dry-run --filter=@furtherverse/desktop (from monorepo root) + 2. Assert: Output shows @furtherverse/server#build runs BEFORE @furtherverse/desktop#build + Expected Result: Server build is listed as a dependency in the dry-run output + Evidence: Terminal output showing task execution order + ``` + + **Commit**: YES (groups with 1) + - Message: `feat(desktop): add build.copy for server bundle and platform configs` + - Files: `apps/desktop/turbo.json` + - Pre-commit: Valid JSON check + +--- + +- [ ] 3. Rewrite `apps/desktop/src/bun/index.ts` — Complete dev/prod mode implementation + + **What to do**: + + This is the core task. Completely rewrite `index.ts` to support both dev and prod modes. The new file should have this structure: + + **A. Imports and Constants**: + ```typescript + import Electrobun, { BrowserWindow } from 'electrobun/bun' + // Import PATHS — verify exact import syntax from electrobun/bun type definitions + // It may be: import { PATHS } from 'electrobun/bun' + // Or it may be on the Electrobun default export: Electrobun.PATHS + // CHECK the type definitions in node_modules/electrobun/ before writing + import { join, dirname } from 'path' + + const DEV_SERVER_URL = 'http://localhost:3000' + const SERVER_READY_TIMEOUT_MS = 30_000 + const PORT_PATTERN = /Listening on:?\s*https?:\/\/[^\s:]+:(\d+)/ + ``` + + **B. `isDev()` function**: + - Check `process.env.ELECTROBUN_BUILD_ENV === 'dev'` + - If `ELECTROBUN_BUILD_ENV` is not set, default to `true` (dev mode) — safe fallback + - Return a boolean + + **C. `getServerEntryPath()` function**: + - Use `PATHS.RESOURCES_FOLDER` (or equivalent) to resolve the bundled server entry + - Path: `join(PATHS.RESOURCES_FOLDER, 'app', 'server-output', 'server', 'index.mjs')` + - **IMPORTANT**: Verify `PATHS.RESOURCES_FOLDER` points to `Resources/` and that `build.copy` destinations land in `Resources/app/`. If the pathing is different, adjust accordingly. The executor MUST verify by checking Electrobun's source or type definitions. + + **D. `waitForServer(url, timeoutMs)` function** (preserved from current code): + - Polls a URL with `fetch` HEAD requests + - Returns `true` when server responds with `response.ok` + - Returns `false` on timeout + - Uses `Bun.sleep(100)` between attempts + - Catches fetch errors silently (server not up yet) + + **E. `spawnServer()` function** (NEW — the critical piece): + - Returns a `Promise<{ process: Subprocess; url: string }>` + - Implementation: + 1. Resolve the server entry path via `getServerEntryPath()` + 2. Resolve the server directory via `dirname(serverEntryPath)` — used as `cwd` + 3. Spawn with `Bun.spawn`: + ```typescript + const serverProc = Bun.spawn([process.execPath, serverEntryPath], { + cwd: serverDir, + env: { + ...process.env, + PORT: '0', + HOST: '127.0.0.1', + }, + stdout: 'pipe', + stderr: 'pipe', + }) + ``` + 4. Read stdout as a stream to find the port: + - Use `serverProc.stdout` (a `ReadableStream`) + - Create a reader, accumulate chunks into a text buffer + - Test buffer against `PORT_PATTERN` regex after each chunk + - When match found: extract port, resolve promise with `{ process: serverProc, url: 'http://127.0.0.1:${port}' }` + 5. Implement a timeout: + - Use `setTimeout` to reject the promise after `SERVER_READY_TIMEOUT_MS` + - On timeout, kill the server process before rejecting + 6. Handle early exit: + - If stdout ends (stream done) before port is found, reject with error + - Include any stderr output in the error message for debugging + + **F. `main()` async function**: + - Log startup message + - Branch on `isDev()`: + - **Dev mode**: + 1. Log: "Dev mode: waiting for external server..." + 2. Call `waitForServer(DEV_SERVER_URL)` + 3. If timeout: log error with instructions (`"Run: bun dev in apps/server"`), `process.exit(1)` + 4. Set `serverUrl = DEV_SERVER_URL` + - **Prod mode**: + 1. Log: "Production mode: starting embedded server..." + 2. Call `spawnServer()` + 3. If error: log error, `process.exit(1)` + 4. Store returned `process` and `url` + - Create `BrowserWindow` with the resolved `serverUrl`: + ```typescript + new BrowserWindow({ + title: 'Furtherverse', + url: serverUrl, + frame: { x: 100, y: 100, width: 1200, height: 800 }, + renderer: 'cef', + }) + ``` + - Register lifecycle handlers: + - `Electrobun.events.on('before-quit', ...)`: Kill server process if it exists + - Watch `serverProcess.exited` (if in prod mode): When server exits unexpectedly, log the exit code and stderr, then `process.exit(1)` + + **G. Top-level execution**: + ```typescript + main().catch((error) => { + console.error('Failed to start:', error) + process.exit(1) + }) + ``` + + **Critical implementation details**: + - The `PORT_PATTERN` regex must handle multiple log formats: + - `➜ Listening on: http://localhost:54321/` (srvx format) + - `Listening on http://127.0.0.1:54321` (node-server format) + - `Listening on http://[::]:54321` (IPv6 format) + - The regex `/Listening on:?\s*https?:\/\/[^\s:]+:(\d+)/` captures the port from all these formats. + - `cwd` MUST be set to the server directory (`dirname(serverEntryPath)`), not the app root. Nitro resolves internal `_libs/` imports relative to its directory. + - `process.execPath` in an Electrobun bundle points to the bundled Bun binary — this is what runs the server. + - `stderr: 'pipe'` — capture stderr for crash diagnostics but don't block on it during startup. + + **Must NOT do**: + - Do not hardcode port numbers anywhere (use `PORT=0` and parse result) + - Do not use `as any` or type assertions to work around issues + - Do not use `child_process` module — use `Bun.spawn` (native Bun API) + - Do not bind server to `0.0.0.0` — always use `127.0.0.1` + - Do not leave the `waitForServer` function unused in dev mode + - Do not use synchronous I/O for stdout reading + + **Recommended Agent Profile**: + - **Category**: `unspecified-high` + - Reason: Complex async logic (stream parsing, subprocess lifecycle, timeout management), multiple code paths (dev/prod), error handling across process boundaries. This is the architectural centerpiece. + - **Skills**: `[]` + - No specialized skills needed — pure Bun/TypeScript with Electrobun APIs + - **Skills Evaluated but Omitted**: + - `frontend-ui-ux`: No UI work — this is backend/process management code + - `playwright`: No browser testing needed for this task + + **Parallelization**: + - **Can Run In Parallel**: YES + - **Parallel Group**: Wave 1 (with Tasks 1, 2) + - **Blocks**: Tasks 4, 5 + - **Blocked By**: None (can start immediately — edits a different file from Tasks 1, 2) + + **References**: + + **Pattern References** (existing code to follow): + - `apps/desktop/src/bun/index.ts` — Current implementation. Preserve the `waitForServer` polling pattern (slightly refactored). Keep the `BrowserWindow` config (title, frame dimensions, renderer). Keep the top-level `main().catch(...)` pattern. + - `apps/desktop/src/bun/index.ts:1` — Current import: `import Electrobun, { BrowserWindow } from 'electrobun/bun'`. Extend this to also import `PATHS` (verify exact export name from type definitions). + + **API/Type References** (contracts to implement against): + - `electrobun/bun` module — Exports `Electrobun` (default), `BrowserWindow` (named), and `PATHS` (named — verify). Check `node_modules/electrobun/` for exact type definitions. + - `Bun.spawn` API — Returns `Subprocess` with `.stdout` (ReadableStream when piped), `.stderr`, `.exited` (Promise), `.kill()`. + - `PATHS.RESOURCES_FOLDER` — Absolute path to `Resources/` directory in the bundle. Verify by reading the Paths.ts source in electrobun package. + + **Documentation References**: + - `apps/desktop/AGENTS.md` — Mentions production mode architecture (aspirational, but gives intent) + + **External References**: + - Electrobun lifecycle events: `Electrobun.events.on('before-quit', callback)` — callback can call `event.response({ allow: false })` to cancel. Source: `electrobun/src/bun/core/Utils.ts`. + - Electrobun `PATHS`: Source at `electrobun/src/bun/core/Paths.ts`. Contains `RESOURCES_FOLDER` and `VIEWS_FOLDER`. + - Bun `Subprocess` docs: `stdout` is `ReadableStream` when `stdout: 'pipe'`. + + **WHY Each Reference Matters**: + - Current `index.ts`: Preserving the `waitForServer` pattern, `BrowserWindow` config, and error handling style. You're rewriting this file, so understand what to keep vs. replace. + - `electrobun/bun` types: MUST verify `PATHS` export name and shape before using it. Don't assume — check. + - `Bun.spawn` API: Core to the entire prod mode implementation. Understand `stdout` stream reading. + - Lifecycle events: `before-quit` is where server cleanup happens. Understand the event contract. + + **Acceptance Criteria**: + - [ ] File compiles: `bun typecheck` passes (from `apps/desktop/`) + - [ ] No hardcoded port numbers (grep for `:3000` — should only appear in `DEV_SERVER_URL` constant) + - [ ] `isDev()` function uses `process.env.ELECTROBUN_BUILD_ENV` + - [ ] `spawnServer()` uses `PORT=0`, `HOST=127.0.0.1`, `process.execPath` + - [ ] `spawnServer()` sets `cwd` to `dirname(serverEntryPath)` + - [ ] `before-quit` handler kills server process + - [ ] Server crash watcher exists (watches `subprocess.exited`) + - [ ] Timeout handling exists in both dev and prod paths + - [ ] All Biome rules pass: `bun fix` produces no changes + + **Agent-Executed QA Scenarios:** + + ``` + Scenario: File compiles with zero type errors + Tool: Bash + Preconditions: File has been rewritten + Steps: + 1. Run: bun typecheck (from apps/desktop/) + 2. Assert: Exit code 0 + 3. Assert: No errors in output + Expected Result: Clean TypeScript compilation + Evidence: Terminal output captured + + Scenario: No hardcoded ports outside DEV_SERVER_URL + Tool: Bash (grep) + Preconditions: File has been rewritten + Steps: + 1. Search apps/desktop/src/bun/index.ts for literal ':3000' + 2. Assert: Only occurrence is in the DEV_SERVER_URL constant definition + 3. Search for literal '3000' — should only appear once + Expected Result: Port 3000 only in constant, nowhere else + Evidence: Grep output + + Scenario: Code passes Biome lint/format + Tool: Bash + Preconditions: File has been rewritten + Steps: + 1. Run: bun fix (from apps/desktop/) + 2. Run: git diff apps/desktop/src/bun/index.ts + 3. Assert: No diff (bun fix made no changes) + Expected Result: Code already conforms to Biome rules + Evidence: Empty git diff + + Scenario: Required patterns present in source + Tool: Bash (grep) + Preconditions: File has been rewritten + Steps: + 1. Grep for 'ELECTROBUN_BUILD_ENV' — Assert: found + 2. Grep for 'Bun.spawn' — Assert: found + 3. Grep for 'process.execPath' — Assert: found + 4. Grep for 'PORT.*0' — Assert: found + 5. Grep for '127.0.0.1' — Assert: found + 6. Grep for 'before-quit' — Assert: found + 7. Grep for '.exited' — Assert: found (crash watcher) + 8. Grep for 'dirname' — Assert: found (cwd for server) + Expected Result: All required patterns present + Evidence: Grep results for each pattern + ``` + + **Commit**: YES + - Message: `feat(desktop): implement production mode with child process server` + - Files: `apps/desktop/src/bun/index.ts` + - Pre-commit: `bun typecheck && bun fix` + +--- + +- [ ] 4. Rewrite `apps/desktop/AGENTS.md` — Document new architecture + + **What to do**: + - Completely rewrite `AGENTS.md` to reflect the actual implemented architecture + - Document: + - **Architecture overview**: Desktop spawns server as child process in prod, connects to external server in dev + - **Dev mode**: How it works (polls localhost:3000, requires server running separately) + - **Prod mode**: How it works (spawns server from bundle, PORT=0, parses port from stdout) + - **Environment detection**: `ELECTROBUN_BUILD_ENV` values (`dev`, `canary`, `stable`) + - **Build pipeline**: Server must build before desktop (turbo dependency), `build.copy` bundles output + - **Key files**: `src/bun/index.ts` (main process), `electrobun.config.ts` (build config) + - **Environment variables**: `DATABASE_URL` (required, passed to server), `ELECTROBUN_BUILD_ENV` (auto-set by CLI) + - **Server lifecycle**: Spawned on start, killed on quit, crash = exit + - **Commands**: `bun dev`, `bun build`, `bun typecheck`, `bun fix` + - Follow the style and conventions of the root `AGENTS.md` and `apps/server/AGENTS.md` + - Be factual — only document what actually exists, not aspirational features + + **Must NOT do**: + - Do not document features that don't exist + - Do not copy content from the server's AGENTS.md verbatim + - Do not include implementation details that belong in code comments + + **Recommended Agent Profile**: + - **Category**: `writing` + - Reason: Documentation task requiring clear technical writing + - **Skills**: `[]` + - No specialized skills needed + - **Skills Evaluated but Omitted**: + - `frontend-ui-ux`: Not a UI task + + **Parallelization**: + - **Can Run In Parallel**: NO + - **Parallel Group**: Wave 2 + - **Blocks**: Task 5 + - **Blocked By**: Tasks 1, 2, 3 (needs to know final state of all files) + + **References**: + + **Pattern References** (existing code to follow): + - `AGENTS.md` (root) — Follow same structure: Overview, Build Commands, Code Style, Directory Structure sections + - `apps/server/AGENTS.md` — Follow same style for app-specific documentation. Use this as a template for tone and detail level. + + **Content References** (what to document): + - `apps/desktop/src/bun/index.ts` — The rewritten file (Task 3 output). Document its behavior, not its code. + - `apps/desktop/electrobun.config.ts` — The updated config (Task 1 output). Document build.copy and platform configs. + - `apps/desktop/turbo.json` — The updated turbo config (Task 2 output). Document the build dependency. + + **WHY Each Reference Matters**: + - Root `AGENTS.md`: Template for documentation style + - Server `AGENTS.md`: Template for app-specific docs + - All Task 1-3 outputs: The actual implemented behavior that must be accurately documented + + **Acceptance Criteria**: + - [ ] File exists and is valid Markdown + - [ ] Documents dev mode behavior accurately + - [ ] Documents prod mode behavior accurately + - [ ] Documents `ELECTROBUN_BUILD_ENV` mechanism + - [ ] Documents build pipeline (server → desktop dependency) + - [ ] Documents `DATABASE_URL` requirement + - [ ] Does NOT mention features that don't exist + - [ ] Follows conventions from root `AGENTS.md` + + **Agent-Executed QA Scenarios:** + + ``` + Scenario: AGENTS.md contains all required sections + Tool: Bash (grep) + Preconditions: File has been rewritten + Steps: + 1. Grep for 'dev' or 'Dev' — Assert: found (dev mode documented) + 2. Grep for 'prod' or 'Prod' or 'production' — Assert: found (prod mode documented) + 3. Grep for 'ELECTROBUN_BUILD_ENV' — Assert: found + 4. Grep for 'DATABASE_URL' — Assert: found + 5. Grep for 'child process' or 'spawn' — Assert: found (architecture documented) + 6. Grep for 'bun dev' — Assert: found (commands documented) + 7. Grep for 'bun build' — Assert: found (commands documented) + Expected Result: All key topics are covered + Evidence: Grep results + + Scenario: No aspirational/unimplemented features documented + Tool: Bash (grep) + Preconditions: File has been rewritten + Steps: + 1. Grep for 'TODO' or 'planned' or 'future' or 'coming soon' — Assert: not found (or minimal) + 2. Grep for 'auto-update' — Assert: not found (not implemented) + 3. Grep for 'tray' — Assert: not found (not implemented) + Expected Result: Only implemented features documented + Evidence: Grep results showing no aspirational content + ``` + + **Commit**: YES + - Message: `docs(desktop): rewrite AGENTS.md to reflect production mode architecture` + - Files: `apps/desktop/AGENTS.md` + - Pre-commit: None (Markdown file) + +--- + +- [ ] 5. End-to-end verification — Typecheck and build pipeline + + **What to do**: + - Run full monorepo typecheck to ensure no type errors were introduced + - Run full monorepo build to verify: + 1. Server builds first (produces `.output/`) + 2. Desktop builds second (copies server output into bundle) + 3. No build errors + - Run Biome formatting/linting check on all changed files + - Verify dev mode still works conceptually (no runtime test — just verify the code path exists) + + **Must NOT do**: + - Do not fix issues in server code — only desktop code + - Do not modify any files unless fixing issues found during verification + - Do not skip any verification step + + **Recommended Agent Profile**: + - **Category**: `quick` + - Reason: Running commands and checking output, no creative work + - **Skills**: `[]` + - No specialized skills needed + - **Skills Evaluated but Omitted**: + - `playwright`: No browser testing in this verification + + **Parallelization**: + - **Can Run In Parallel**: NO + - **Parallel Group**: Wave 2 (sequential after Task 4) + - **Blocks**: None (final task) + - **Blocked By**: Tasks 1, 2, 3, 4 + + **References**: + + **Documentation References**: + - `AGENTS.md` (root) — Build/Lint/Test commands: `bun typecheck`, `bun fix`, `bun build` + - `apps/desktop/package.json` — Desktop-specific scripts + + **WHY Each Reference Matters**: + - Root `AGENTS.md`: Canonical list of verification commands + - Desktop `package.json`: Desktop-specific build/typecheck commands + + **Acceptance Criteria**: + - [ ] `bun typecheck` (monorepo root) exits with code 0 + - [ ] `bun build` (monorepo root) exits with code 0 + - [ ] `bun fix` (monorepo root) produces no changes (all code formatted) + - [ ] Build output shows server building before desktop + - [ ] Desktop build output includes server bundle (verify in build artifacts) + + **Agent-Executed QA Scenarios:** + + ``` + Scenario: Monorepo typecheck passes + Tool: Bash + Preconditions: All tasks 1-4 completed + Steps: + 1. Run: bun typecheck (from monorepo root) + 2. Assert: Exit code 0 + 3. Assert: No error output + Expected Result: Zero type errors across entire monorepo + Evidence: Terminal output captured + + Scenario: Monorepo build succeeds with correct order + Tool: Bash + Preconditions: All tasks 1-4 completed + Steps: + 1. Run: bun build (from monorepo root) + 2. Assert: Exit code 0 + 3. Assert: Output shows @furtherverse/server build task runs + 4. Assert: Output shows @furtherverse/desktop build task runs AFTER server + Expected Result: Build pipeline executes in correct order + Evidence: Terminal output showing task order + + Scenario: Biome finds no issues + Tool: Bash + Preconditions: All tasks 1-4 completed + Steps: + 1. Run: bun fix (from monorepo root) + 2. Run: git diff + 3. Assert: No changes (all code already formatted) + Expected Result: All code passes Biome rules + Evidence: Empty git diff + + Scenario: Desktop build artifacts include server bundle + Tool: Bash + Preconditions: Build succeeded + Steps: + 1. Search desktop build output directory for server-output/ or index.mjs + 2. Assert: Server files are present in the desktop bundle + Expected Result: Server bundle is included in desktop build output + Evidence: File listing of build artifacts + ``` + + **Commit**: NO (verification only — no file changes unless fixing issues) + +--- + +## Commit Strategy + +| After Task(s) | Message | Files | Verification | +|---------------|---------|-------|--------------| +| 1, 2 | `feat(desktop): add build.copy for server bundle and cross-workspace build dependency` | `electrobun.config.ts`, `turbo.json` | `bun typecheck` | +| 3 | `feat(desktop): implement production mode with child process server` | `src/bun/index.ts` | `bun typecheck && bun fix` | +| 4 | `docs(desktop): rewrite AGENTS.md to reflect production mode architecture` | `AGENTS.md` | None | +| 5 | (no commit — verification only) | — | — | + +--- + +## Success Criteria + +### Verification Commands +```bash +bun typecheck # Expected: exit 0, no errors +bun build # Expected: exit 0, server builds before desktop +bun fix # Expected: no changes (already formatted) +``` + +### Final Checklist +- [ ] All "Must Have" features present in `index.ts` +- [ ] All "Must NOT Have" exclusions verified absent +- [ ] All 3 verification commands pass +- [ ] `AGENTS.md` accurately reflects implemented architecture +- [ ] Server output is bundled into desktop build via `build.copy` +- [ ] Turbo builds server before desktop diff --git a/apps/desktop/AGENTS.md b/apps/desktop/AGENTS.md index 0e4214c..aeec0df 100644 --- a/apps/desktop/AGENTS.md +++ b/apps/desktop/AGENTS.md @@ -1,124 +1,130 @@ # AGENTS.md - Desktop App Guidelines -Electrobun desktop shell — loads the server app in a native window. +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 -- **Type**: Electrobun desktop application -- **Design**: Bun main process + CEF renderer (Chromium Embedded Framework) -- **Dev mode**: Connects to `localhost:3000` (requires server dev running) -- **Prod mode**: Embeds server bundle and starts local HTTP server -- **Config**: `electrobun.config.ts` — app metadata, build entrypoint, platform options +- **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 (from apps/desktop/) -bun dev # Start Electrobun dev mode +# Development +bun dev # Start Electrobun dev mode (requires server dev running) # Build -bun build # Build canary release (current platform) -bun build:all # Build canary release (all platforms) -bun build:stable # Build stable release (current platform) -bun build:stable:all # Build stable release (all platforms) +bun build # Build stable release (all platforms) # Code Quality -bun fix # Biome auto-fix (lint + format) +bun fix # Biome auto-fix bun typecheck # TypeScript check ``` ## Directory Structure ``` -apps/desktop/ +. ├── src/ │ └── bun/ -│ └── index.ts # Main process entry (BrowserWindow + server wait) -├── electrobun.config.ts # App name, identifier, version, build config -├── package.json -├── tsconfig.json # Extends @furtherverse/tsconfig/bun.json -└── turbo.json # Build output config +│ └── 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 dev first**: `bun dev` from `apps/server/` -2. **Start Electrobun**: `bun dev` from `apps/desktop/` -3. Desktop app polls `localhost:3000` until server responds, then opens window +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**: Server is started with `PORT=0` and `HOST=127.0.0.1`. +- **Port Detection**: The main process parses the server's `stdout` using a regex to find the dynamically assigned port. +- **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 -### Config (`electrobun.config.ts`) +### BrowserWindow Configuration +The main window uses the CEF renderer for consistency across platforms. ```typescript -import type { ElectrobunConfig } from 'electrobun' - -export default { - app: { - name: 'MyApp', - identifier: 'com.example.myapp', - version: '0.1.0', - }, - build: { - bun: { entrypoint: 'src/bun/index.ts' }, - linux: { bundleCEF: true }, - }, -} satisfies ElectrobunConfig -``` - -### BrowserWindow - -```typescript -import { BrowserWindow } from 'electrobun/bun' - new BrowserWindow({ - title: 'My App', - url: 'http://localhost:3000', - frame: { x: 100, y: 100, width: 1200, height: 800 }, + title: 'Furtherverse', + url: serverUrl, + frame: { + x: 100, + y: 100, + width: 1200, + height: 800, + }, renderer: 'cef', }) ``` -### Server Readiness Check +### Path Aliases +The main process uses `electrobun/bun` for native APIs and `PATHS` for locating bundled assets. ```typescript -// Poll server before opening window — don't block indefinitely -async function waitForServer(url: string, timeoutMs = 30000): Promise { - const start = Date.now() - while (Date.now() - start < timeoutMs) { - try { - const response = await fetch(url, { method: 'HEAD' }) - if (response.ok) return true - } catch { - await Bun.sleep(100) - } - } - return false -} -``` +import { BrowserWindow, PATHS } from 'electrobun/bun' -### Application Events - -```typescript -import Electrobun from 'electrobun/bun' - -Electrobun.events.on('will-quit', () => { - console.log('App quitting...') -}) +// Locate the embedded server bundle +const serverEntryPath = join(PATHS.VIEWS_FOLDER, '..', 'server-output', 'server', 'index.mjs') ``` ## Critical Rules **DO:** -- Run server dev before desktop dev -- Use `catalog:` for dependency versions -- Wait for server readiness before opening BrowserWindow -- Handle server timeout gracefully (exit with helpful error message) +- Use arrow functions for all components and utility functions. +- Ensure `apps/server` is built before building `apps/desktop` (handled by Turbo). +- Parse the server's stdout for the port instead of hardcoding ports in production. +- Handle server process termination to avoid orphan processes. +- Use `catalog:` for all dependency versions in `package.json`. **DON'T:** -- Use `npm`, `npx`, `node`, `yarn`, `pnpm` — always use `bun` / `bunx` -- Hardcode dependency versions (use catalog) -- Block main thread with synchronous waits -- Use `unwrap()`-style patterns without error handling +- 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`.