Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,16 @@ npm run dev:stop # stop the dev runtime
npm stop dev # same as dev:stop
```

Browser preview of the desktop renderer (UI work without Electron):

```bash
cd apps/desktop
npm run dev:vite # mock-only: synthetic window.ade, fast shell
ADE_PROJECT_ROOT=/path/to/project npm run dev:vite:live # mock + live runtime bridge (Linear, sync, lanes)
```

`dev:vite:live` starts the ADE dev runtime, a localhost HTTP bridge to the runtime socket, and Vite with a proxy so the browser can call real backend methods on top of the mock. Set `ADE_PROJECT_ROOT` to your primary project checkout (where `.ade/` and secrets live), especially when working from a lane worktree. Full details: [apps/desktop/README.md](apps/desktop/README.md).

The dev commands intentionally use a temp socket and a separate Electron profile so they do not collide with the installed ADE app:

```text
Expand Down
4 changes: 1 addition & 3 deletions apps/ade-cli/src/tuiClient/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,6 @@ export function mergeOptimisticChatSessions(
optimisticSessions.delete(sessionId);
}
const pending = [...optimisticSessions.values()]
.filter((session) => !seen.has(session.sessionId))
.sort((left, right) => {
const rightMs = Date.parse(right.lastActivityAt ?? right.startedAt);
const leftMs = Date.parse(left.lastActivityAt ?? left.startedAt);
Expand Down Expand Up @@ -1417,8 +1416,7 @@ export function isPromptWordBackspace(input: string, key: { ctrl?: boolean; meta
}

export function isPromptLineBackspace(input: string, key: { ctrl?: boolean; meta?: boolean; backspace?: boolean; delete?: boolean }): boolean {
if (isCtrlInput(input, key, "u")) return true;
return false;
return isCtrlInput(input, key, "u");
}

type PromptEditResult = { value: string; cursor: number };
Expand Down
158 changes: 158 additions & 0 deletions apps/desktop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# ADE Desktop

Electron client for ADE. The renderer is also runnable in a regular browser for fast UI iteration.

## Surfaces

| Surface | Command | `window.ade` source | Backend |
|--------|---------|---------------------|---------|
| **Desktop dev** | `npm run dev` (repo root) | Electron preload β†’ main IPC β†’ runtime socket | Full |
| **Browser preview (mock)** | `npm run dev:vite` | `browserMock.ts` only | Synthetic demo data |
| **Browser preview (live)** | `npm run dev:vite:live` | Mock + runtime bridge patches | Partial live (see below) |

`apps/web` is the public marketing site. This document covers the **desktop renderer in a browser** (`localhost:5173`), not the marketing app.

## How the browser preview works

Opening `http://localhost:5173` without Electron loads the same React renderer as the desktop app, but there is no preload bridge. On startup:

1. **`browserMock.ts`** (imported first in `main.tsx`) installs a full `window.ade` stub so the UI can render without crashing. It returns built-in demo data for PRs, lanes, sessions, git, and so on.
2. **`attachBrowserRuntimeBridge()`** (called at the end of the mock install) probes `GET /ade-dev-rpc/health`. If the browser runtime bridge is running, it **patches** selected methods on top of the mock and dispatches `ade:runtime-bridge-ready`.

```text
Browser tab
└─ window.ade (mock baseline)
└─ patched methods β†’ fetch /ade-dev-rpc/*
└─ Vite proxy
└─ browser-runtime-bridge.mjs (127.0.0.1:18765)
└─ JSON-RPC β†’ /tmp/ade-runtime-dev.sock
└─ ade serve (same daemon as desktop dev)
```

The mock stays the fallback for everything the bridge does not override. UI work that only reads mock data still works with `dev:vite` alone.

## Launch

### Mock-only (fast UI shell)

From `apps/desktop`:

```bash
npm run dev:vite
```

Optional: export SQLite snapshot so lanes, PRs, sessions, and run-tab config mirror a real project:

```bash
ADE_PROJECT_ROOT=/path/to/your/project npm run export:browser-mock-ade
npm run dev:vite
```

The export runs automatically (best-effort) before `dev:vite` via `predev:vite`. Output:

`src/renderer/browser-mock-ade-snapshot.generated.json`

That file is gitignored. It seeds **read-only** mock data from `.ade/ade.db` at export time. It does **not** include secrets (Linear tokens, API keys).

### Live bridge (real Linear, sync, lanes)

From `apps/desktop`:

```bash
ADE_PROJECT_ROOT=/path/to/your/project npm run dev:vite:live
```

This script:

1. Builds/refreshes the ADE CLI runtime if needed
2. Ensures the dev runtime is listening on `/tmp/ade-runtime-dev.sock` (override with `ADE_DEV_RUNTIME_SOCKET_PATH`)
3. Starts `browser-runtime-bridge.mjs` on `127.0.0.1:18765` (`ADE_BROWSER_BRIDGE_PORT` to override)
4. Starts Vite on port 5173 with a proxy from `/ade-dev-rpc` β†’ the bridge

Open `http://localhost:5173` (or `http://127.0.0.1:5173`).

**Lane worktrees:** if you run from `.ade/worktrees/<lane>`, set `ADE_PROJECT_ROOT` to the primary project checkout (where `.ade/ade.db` and secrets live), not the worktree path. Same rule as `npm run dev`.

Skip runtime rebuild when the CLI is already fresh:

```bash
ADE_PROJECT_ROOT=/path/to/your/project npm run dev:vite:live -- --skip-runtime-build
```

Bridge only (Vite already running):

```bash
ADE_PROJECT_ROOT=/path/to/your/project npm run dev:browser-bridge
```

Verify the bridge:

```bash
curl -s http://127.0.0.1:18765/health | jq .
```

## What the live bridge covers today

When the bridge attaches, these `window.ade` methods call the real runtime instead of the mock:

| Area | Methods |
|------|---------|
| **Project** | `app.getProject`, `app.getWindowSession` β€” real `projectRoot` / `projectId` from `projects.add` |
| **Linear** | `cto.getLinearConnectionStatus`, `getLinearQuickView`, `getLinearIssuePickerData`, `searchLinearIssues`, `getLinearProjects`, `setLinearToken`, `clearLinearToken` |
| **Sync / mobile** | `sync.getStatus`, `refreshDiscovery`, `listDevices`, `updateLocalDevice`, `connectToBrain`, `disconnectFromBrain`, `forgetDevice`, `getTransferReadiness`, `transferBrainToLocal`, `getPin`, `setPin`, `generatePin`, `clearPin` |
| **Lanes** | `lanes.create`, `lanes.list` (e.g. Linear quick view β†’ create lane) |

Linear must already be connected in that project (Settings β†’ Linear, or token in encrypted store under `.ade/secrets`). The bridge uses the same credentials as desktop dev.

## Still mock-only in the browser

Even with `dev:vite:live`, these stay on the mock until wired to the bridge or another backend:

- Terminals / PTY / live chat sessions
- PR list/detail/actions, git read/write, files on disk
- Remote runtime connection UI (Electron IPC)
- Computer use, App Control, iOS simulator, Mac VM
- Agent chat send/receive, orchestration runs
- Most settings persistence beyond Linear token via bridge

For full product behavior, use **`npm run dev`** (Electron + preload).

## Seeding mock data

Three layers, from lightest to richest:

1. **Built-in demo** β€” no setup; synthetic lanes, PRs, sessions in `browserMock.ts`.
2. **Snapshot export** β€” `npm run export:browser-mock-ade` with `ADE_PROJECT_ROOT` set; replaces demo rows with data from `.ade/ade.db` and optional disk walks for the Files tab.
3. **Live bridge** β€” real runtime for Linear, sync, and lane create/list; mock still backs everything else unless you also export a snapshot for read-only parity.

Re-export the snapshot after local DB changes you want reflected in mock-only mode:

```bash
ADE_PROJECT_ROOT=/path/to/your/project npm run export:browser-mock-ade
```

## Environment variables

| Variable | Purpose |
|----------|---------|
| `ADE_PROJECT_ROOT` | Project opened by the bridge and snapshot export (primary checkout, not lane worktree) |
| `ADE_DEV_RUNTIME_SOCKET_PATH` | Dev runtime socket (default `/tmp/ade-runtime-dev.sock`) |
| `ADE_BROWSER_BRIDGE_PORT` | Bridge HTTP port (default `18765`) |

## Related files

| File | Role |
|------|------|
| `src/renderer/browserMock.ts` | Full `window.ade` stub for browser |
| `src/renderer/browserRuntimeBridge.ts` | Patches live methods when bridge is up |
| `scripts/browser-runtime-bridge.mjs` | HTTP β†’ runtime JSON-RPC |
| `scripts/dev-vite-live.mjs` | Orchestrates runtime + bridge + Vite |
| `scripts/export-browser-mock-ade-snapshot.mjs` | SQLite β†’ mock snapshot JSON |
| `vite.config.ts` | Proxies `/ade-dev-rpc` to the bridge |

## Validation

```bash
npm run typecheck
npm run test:unit -- src/renderer/components/app/TopBar.test.tsx
```
2 changes: 2 additions & 0 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"dev:clean": "node ./scripts/clear-vite-cache.cjs && node ./scripts/normalize-runtime-binaries.cjs && node ./scripts/ensure-electron.cjs && node ./scripts/dev.cjs --force-vite",
"predev:vite": "node ./scripts/export-browser-mock-ade-snapshot.mjs --optional",
"dev:vite": "vite --port 5173 --strictPort",
"dev:vite:live": "node ./scripts/dev-vite-live.mjs",
"dev:browser-bridge": "node ./scripts/browser-runtime-bridge.mjs",
"export:browser-mock-ade": "node ./scripts/export-browser-mock-ade-snapshot.mjs",
"build": "tsup && vite build",
"dist:win": "npm run materialize:runtime-resources && npm run validate:runtime-resources && npm run validate:win:artifacts && npm run build && electron-builder --win --x64 --publish never && npm run validate:win:release",
Expand Down
22 changes: 22 additions & 0 deletions apps/desktop/resources/agent-skills/ade-app-control/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,25 @@ ade --socket app-control terminal signal --signal SIGINT

Only fall back to `ade --socket terminal list --text` and `ade --socket terminal read ...` when no App Control terminal is active.

## Launching ADE itself from inside ADE

If you are an agent running inside one ADE instance (e.g. ADE Beta or stable) and you need to launch the ADE dev desktop app under App Control, the dev launcher is already isolated and safe to run:

```bash
ade --socket app-control launch --command "npm run dev" --text
```

`npm run dev` uses its own runtime socket (`/tmp/ade-runtime-dev.sock`) and a separate Electron profile (`ade-desktop-dev`), so it will not collide with the runtime/socket that is hosting you. Confirm with `ade runtime status --text` before launching β€” that tells you which socket the CLI is currently attached to.

### Survive Electron restarts

`npm run dev` watches `apps/desktop/src/main/**` and restarts Electron whenever the main bundle rebuilds. After a restart, the App Control drawer UI in the parent ADE window can show stale `Waiting for CDP on 127.0.0.1:<port>` even though the new renderer is already exposed on the same port. From the CLI you can confirm and re-bind:

```bash
ade --socket app-control targets --text # find the new page target id
ade --socket app-control attach-target --target <id> --text
ade --socket app-control snapshot --text # forces the drawer to repaint
```

If `targets` shows a `/devtools/page/<id>` entry with the dev URL (`http://localhost:5173/...`), CDP is healthy β€” the drawer banner is just lagging until the next snapshot.

Loading
Loading