Desktop Application Skill
When to use
- Starting a new desktop application on Windows, macOS, Linux, or cross-platform
- Adding native OS integration: system tray, file associations, OS notifications, clipboard, auto-launch
- Packaging and signing for distribution (MSI/EXE, DMG/PKG, AppImage/DEB/RPM, MSIX)
- Implementing auto-update with staged rollouts
- Diagnosing memory leaks, main-thread blocking, or IPC latency
Workflow
Classify the project — Read
docs/specs/product-spec.mdand.claude/stack-matrix/to confirm target OS(es), distribution channel (direct download, Microsoft Store, Mac App Store, homebrew cask, Flathub), and the tech stack. Confirm whether web-tech (Electron/Tauri) or native UI is required.Choose framework
Requirement Recommended Ship fast, existing web team Tauri (Rust backend, web frontend, ~3 MB binary) or Electron Maximum bundle size constraint (<5 MB) Tauri Deep Windows shell integration .NET WinForms / WPF / WinUI 3 Deep macOS integration (SwiftUI, Handoff, Continuity) Swift + AppKit/SwiftUI Cross-platform with native look Qt 6 or .NET MAUI JVM ecosystem Compose Desktop / JavaFX Design the process model — For multi-process frameworks (Electron, Tauri):
- Main / backend process: owns system resources, file I/O, native APIs
- Renderer / frontend: UI only — no direct filesystem or native calls
- Define the IPC contract (channel names, payload schemas) before coding. Validate payloads with Zod or equivalent on both sides.
Implement IPC securely — Electron: use
contextBridge.exposeInMainWorldto whitelist exactly the functions the renderer needs; never enablenodeIntegration: truein renderer. Tauri: declare commands intauri.conf.jsonallow-list.Handle window management — Create a
WindowManagerservice that:- Persists last window size/position to app prefs and restores on launch
- Handles multiple windows gracefully (reference by label/ID, not index)
- Respects OS conventions: cmd+W on macOS hides, does not quit; Alt+F4 on Windows quits
Integrate native OS features as needed
- System tray: always provide a quit action; optionally show app status
- File associations: register in manifest/plist; handle
open-fileevents at startup and while running - Deep links: register custom URL scheme; handle both cold-start and already-running cases
- Auto-launch at login: provide an explicit toggle in settings; never enable silently
Persist user data — Use OS-standard locations:
- Config:
app.getPath('userData')(Electron) or platform-standard (~/Library/Application Support,%APPDATA%,~/.config) - Documents: only with user's explicit consent via file picker
- Never write to the application bundle itself (breaks signed apps)
- Config:
Implement auto-update
- Electron:
electron-updaterwith a private S3/GCS bucket or GitHub Releases - Tauri: built-in updater with signed update manifests
- Test the update flow on every OS before each release. Provide a manual "Check for updates" menu item.
- Electron:
Package and sign
- macOS: codesign with Developer ID + notarise with
xcrun notarytool; staple the ticket - Windows: sign with EV or OV code-signing certificate; avoid SmartScreen warnings
- Linux: provide AppImage + distro package (DEB/RPM) or Flatpak; AppImage is most universal Never ship an unsigned binary for production; users will face security warnings.
- macOS: codesign with Developer ID + notarise with
Write tests
- Unit: pure logic, IPC command handlers, data persistence
- Integration: Playwright Desktop (Electron), Tauri's WebDriver, or OS-native test frameworks
Reference
.claude/checklists/qa.md.
Run production-readiness checklist — Reference
.claude/checklists/production.md. Confirm: crash reporter integrated (Sentry, BugSnag, or native), no dev-mode flags in production build, update channel set to stable.
Standards
Do
- Validate all data crossing the IPC boundary as if it were an untrusted network call.
- Keep the main/backend process lean; push heavy computation to worker threads or child processes.
- Use OS native dialogs (
dialog.showOpenDialog,NSSavePanel) for file operations — never custom HTML pickers for security-sensitive paths. - Include
Content-Security-Policyin the<meta>tag of every HTML page loaded in Electron (restrictdefault-src, banunsafe-eval). - Ship a universal macOS binary (arm64 + x86_64) unless the target hardware is known.
Do not
- Enable
nodeIntegration: trueorcontextIsolation: falsein Electron renderer processes. - Load remote URLs in Electron's main window — disable navigation with
will-navigatehandler. - Write sensitive data (tokens, keys) to plain text files; use the OS credential store (Keychain, Windows Credential Manager, libsecret).
- Hard-code update server URLs; configure them via environment/build config so channels (stable, beta) can differ.
- Block the UI thread with synchronous disk reads; use async APIs.
Common mistakes to avoid
- No quit shortcut on macOS — On macOS the Dock icon click should re-open the window if hidden, not launch a new instance; implement
activateevent handler. - Missing
app.requestSingleInstanceLock()in Electron — users can launch multiple instances and corrupt shared state. - IPC payload injection — If any part of an IPC payload is constructed from user-supplied strings, sanitise before use; arbitrary
evalorshell.openExternalcan be triggered. - Forgetting Windows-specific paths —
path.joinis safe; string concatenation with/breaks on Windows. - Not handling offline startup — If the app phones home on startup and the network is down, it should still open gracefully.
- Bundling dev dependencies — Electron-builder
filesconfig must excludenode_modulesdev deps; bloat adds seconds to install. - Skipping code signing during development — differences between signed and unsigned code can mask permission bugs discovered only at signing time.
Output format
A production-ready desktop project should include:
/
├── src/
│ ├── main/ # Main / backend process (IPC handlers, native API)
│ ├── renderer/ # UI (React, Vue, Svelte, or native UI code)
│ ├── shared/ # Types, IPC channel constants shared by both processes
│ └── preload/ # Electron preload scripts (context bridge)
├── resources/ # App icons per OS (icns, ico, png)
├── build/ # Packaging config (electron-builder.yml, tauri.conf.json)
├── tests/
├── .env.example
└── README.md # Build, sign, and distribute steps per OS
Related checklists
.claude/checklists/security.md.claude/checklists/performance.md.claude/checklists/qa.md.claude/checklists/production.md
Related agents
.claude/agents/core/orchestrator.md.claude/agents/quality/security-auditor.md