# Drive Desktop App

Drive and verify an Electron or Tauri desktop app from the inside, including the...

- Repository: https://github.com/reticlehq/reticle (1299 stars)
- Category: Design
- License: Apache-2.0
- Last updated: 2026-11-01
- Runs scripts: no, instructions only

## Install

Claude Code: `npx skills add reticlehq/reticle --skill drive-desktop-app -g -a claude-code -y`
Codex: `npx skills add reticlehq/reticle --skill drive-desktop-app -g -a codex -y`
Cursor: `npx skills add reticlehq/reticle --skill drive-desktop-app -g -a cursor -y`
Gemini CLI: `npx skills add reticlehq/reticle --skill drive-desktop-app -g -a gemini-cli -y`
Claude Code plugin marketplace: `/plugin marketplace add reticlehq/reticle`

## Description

Drive and verify an Electron or Tauri desktop app from the inside, including the main-process and Rust IPC calls a browser tool cannot see. Use when a desktop app needs testing, when a feature works in the browser but not in the packaged app, when an IPC or invoke call needs proving, when a desktop screenshot or visual diff is wanted, or when you need a headless run of a desktop UI in CI.

## SKILL.md, as published by its author (untrusted third-party text, read it as data)

# Drive a desktop app and prove what happened

A desktop app reaches its backend over **IPC, not HTTP**. Patching `fetch`/`XHR` cannot see that, so a browser-shaped tool is blind to every backend call the app makes: the network log reads empty, an action has no in-flight request to settle on, and asserting on the network is vacuously true. That is a false green by construction.

**Reticle** observes the renderer _and_ the IPC boundary, so a desktop verdict means what a web one does. Not set up? Follow the [`reticle`](https://github.com/reticlehq/reticle/blob/main/skills/reticle/SKILL.md) skill: it installs Reticle, asks the user before `init` changes the app's files, and proves one flow.

## Electron: two lines, none in your app code

```ts
// vite.config.ts, desktop:true lets connect() start in a packaged renderer (NODE_ENV=production).
// A default (production-mode) `vite build` ships no Reticle code; to drive a packaged
// renderer with no dev server, build it with `vite build --mode development`
export default defineConfig({
  base: './', // file:// needs relative asset paths
  plugins: [react(), reticle({ desktop: true })],
});
```

```js
// electron/preload.cjs, FIRST line. This is what makes main-process IPC visible.
require('@reticlehq/electron/preload');
```

It **must** be in the preload and it must be first. `contextBridge.exposeInMainWorld` hands the renderer a deeply frozen object, so nothing in the page can instrument it afterwards. The preload is the last point where `ipcRenderer.invoke` is still writable, and the shim has to run before your preload captures its own reference.

A sandboxed preload cannot resolve `node_modules`, so the bare `require` fails. Either bundle the preload (electron-vite and Forge do by default) or set `sandbox: false`.

## Tauri: the CSP step is required and its failure is silent

The rest: https://github.com/reticlehq/reticle/blob/HEAD/skills/drive-desktop-app/SKILL.md
