reticlehq/reticle

drive-desktop-app

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.

First seen Aug 15, 2026

Installation

$ npx skills add reticlehq/reticle --skill drive-desktop-app

Summary

  • 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.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from reticlehq/reticle · top by installs.

npx skills add reticlehq/reticle

Browse all from reticlehq/reticle

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 451
License LICENSE
Default branch main
Open issues 67
Status Active

Skill metadata

Parsed from SKILL.md frontmatter.

Version2.13.1
LicenseApache-2.0
More metadata
version
2.13.1
homepage
https://www.reticle.sh
repository
https://github.com/reticlehq/reticle

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 5,079 B
  • docs SUMMARY.md 416 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 78 installs

SKILL.md

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 installed? RETICLEINSTALLSOURCE=npx_skill npx @reticlehq/server@latest init, then the install-and-verify skill.

Electron: two lines, none in your app code

// vite.config.ts — desktop:true also runs the plugin for `vite build`, because a packaged
// renderer is a production build with no dev server
export default defineConfig({
  base: './', // file:// needs relative asset paths
  plugins: [react(), reticle({ desktop: true })],
});
// 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 frontend is the same as any web app. The part people miss is that Tauri's default CSP blocks the bridge WebSocket before it opens, so the app runs perfectly and simply never connects:

{
  "app": {
    "security": {
      "csp": "default-src 'self' ipc: http://ipc.localhost; connect-src 'self' ipc: http://ipc.localhost ws://localhost:4400 ws://127.0.0.1:4400"
    }
  }
}

Keep ipc: http://ipc.localhost: Tauri v2 needs it for invoke itself. Dev-only; drop the ws:// entries from your release config.

IPC observation needs nothing on the Rust side: an invoke('loadtodos') already reaches Reticle as ipc://loadtodos. The reticle-tauri crate is only for screenshots and headless, and it is versioned independently of the npm packages.

Also: use a hash router. A packaged renderer is served from file://, where history-based routing does not resolve.

Verify

Same loop as the web, with IPC in the predicates:

reticle_act_and_wait({ sessionId, ref, action: "click", until: { kind: "allOf", predicates: [
  { kind: "net",     urlContains: "ipc://todos:archive", status: 200 },
  { kind: "element", query: { testid: "..." } },
  { kind: "console", level: "error", absent: true },
]}})

IPC has no status code. 200/500 are synthetic, mapped from whether the command succeeded, precisely so the same predicates keep working. On Tauri you will see status: 500 next to statusText: "OK". That is not a bug: the transport answered fine and the 500 is the command's own verdict. ok is authoritative.

reticlestate reads the live store exactly as on the web. reticlescreenshot and reticlevisualdiff work once the platform's capture step is wired. Electron needs nothing extra; Tauri needs the crate. Headless on Tauri is RETICLE_HEADLESS=1, and screenshots keep working because the capture renders the webview rather than the screen.

What a missing observer looks like

A missing Electron preload is declared, not silent: verdicts come back with coverage: partial naming the line you did not add, instead of reading clean over a blind spot. If you see that, add the preload line before trusting anything.

If IPC calls never appear while the app works fine: on Electron, the shim's require is not first. On Tauri, invoke from @tauri-apps/api/core is observed, but a hand-rolled postMessage protocol is not.

Honesty

unknown is not a pass on the desktop either. And do not weaken an IPC assertion to make a red verdict green: a desktop false green is the exact failure this wiring exists to remove.


Full desktop reference: curl https://docs.reticle.sh/desktop.md. Everything else: curl https://docs.reticle.sh/llms.txt.