
electron-dev
Electron desktop apps with React, TypeScript, and Vite. Use for IPC, window/tray, PTY terminals, WebRTC, and packaging.
- Installs
- 305
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/jamditis/claude-skills-journalism --skill electron-dev- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads electron-dev automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
Electron desktop development
Patterns and practices for building production-quality Electron applications with React and TypeScript.
Security baseline (Electron 30+)
Electron's defaults have hardened over the past several releases. As of Electron 28+, contextIsolation: true and sandbox: true are the defaults for new BrowserWindow instances, most security advice from older guides assumed you had to opt in. You don't anymore; you have to opt OUT, and you should not.
Set explicitly anyway, so a config drift never weakens the security model:
const win = new BrowserWindow({
webPreferences: {
contextIsolation: true, // default since 12, mandatory for any prod app
sandbox: true, // default since 28; renderer runs sandboxed
nodeIntegration: false, // never enable in renderer
webSecurity: true, // never disable
preload: path.join(__dirname, 'preload.cjs')
}
});
Validate every IPC message in main. Don't trust the renderer.
Electron Fuses + ASAR integrity
Electron Fuses are package-time toggles baked into the binary. The two relevant for security distribution:
EnableEmbeddedAsarIntegrityValidation, verifies the app.asar hash at runtime against a hash embedded in the binary. Defends against attackers swapping the asar contents post-install.OnlyLoadAppFromAsar, refuses to load app code from anywhere except the validated asar.
These are opt-in, not default. Enable both for production. Requires @electron/asar 3.1.0+ to generate the asar with embeddable integrity. electron-builder configures this via electronFuses in the build config; @electron/fuses does it programmatically.
CVE-2023-44402 (ASAR integrity bypass via filetype confusion) was the canonical motivation here, without integrity + only-load-from-asar, an attacker who can modify app files can swap behavior silently.
Common renderer-side risks
- Preload script confusion, only expose narrow, typed surfaces via
contextBridge.exposeInMainWorld. Never re-exportipcRendereritself; expose specific methods that map to specific channels. file://IPC and navigation, restrict navigation withwebContents.on('will-navigate', e => e.preventDefault())for windows that shouldn't change URL. DenysetWindowOpenHandlerrequests by default; allow-list specific origins.shell.openExternalwith user input, validate the URL scheme before opening. An attacker-controlledfile://orjavascript:URL hands them code execution.
Architecture patterns
Project structure
app/
├── electron/
│ ├── main.cjs # Main process (CommonJS required)
│ ├── preload.cjs # Context bridge for secure IPC
│ └── server.cjs # Optional: WebSocket/HTTP server
├── src/
│ ├── components/ # React components
│ ├── services/ # Business logic (API clients, Firebase)
│ ├── utils/ # Utilities (audio, formatting)
│ ├── types.ts # TypeScript interfaces
│ ├── App.tsx # Root component
│ └── index.tsx # React entry
├── assets/ # Icons, sounds, images
├── package.json
├── vite.config.ts
└── electron-builder.yml # Build configuration
IPC communication pattern
Main process (main.cjs):
const { ipcMain } = require('electron');
// Handle async requests from renderer
ipcMain.handle('action-name', async (event, args) => {
try {
const result = await someAsyncOperation(args);
return { success: true, data: result };
} catch (error) {
return { success: false, error: error.message };
}
});
// Send data to renderer
mainWindow.webContents.send('event-name', data);
Preload script (preload.cjs):
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electron', {
actionName: (args) => ipcRenderer.invoke('action-name', args),
onEventName: (callback) => {
const handler = (event, data) => callback(data);
ipcRenderer.on('event-name', handler);
return () => ipcRenderer.removeListener('event-name', handler);
}
});
Renderer (React):
const result = await window.electron.actionName(args);
useEffect(() => {
return window.electron.onEventName((data) => {
setState(data);
});
}, []);
System tray integration
const { Tray, Menu, nativeImage } = require('electron');
let tray = null;
function createTray() {
const icon = nativeImage.createFromPath(path.join(__dirname, '../assets/tray-icon.png'));
tray = new Tray(icon.resize({ width: 16, height: 16 }));
tray.setToolTip('App Name');
tray.setContextMenu(Menu.buildFromTemplate([
{ label: 'Show', click: () => mainWindow.show() },
{ label: 'Quit', click: () => app.quit() }
]));
tray.on('click', () => {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
});
}
// Hide to tray instead of closing
mainWindow.on('close', (event) => {
if (!app.isQuitting) {
event.preventDefault();
mainWindow.hide();
}
});
Global shortcuts
const { globalShortcut } = require('electron');
app.whenReady().then(() => {
// Register with conflict detection
const registered = globalShortcut.register('Alt+S', () => {
mainWindow.webContents.send('shortcut-triggered', 'toggle-recording');
});
if (!registered) {
console.error('Shortcut registration failed - conflict detected');
}
});
app.on('will-quit', () => {
globalShortcut.unregisterAll();
});
PTY terminal integration (node-pty)
const pty = require('node-pty');
const shell = process.platform === 'win32' ? 'powershell.exe' : process.env.SHELL || '/bin/bash';
const ptyProcess = pty.spawn(shell, [], {
name: 'xterm-256color',
cols: 80,
rows: 24,
cwd: process.env.HOME,
env: process.env
});
ptyProcess.onData((data) => {
mainWindow.webContents.send('terminal-data', { tabId, data });
});
ipcMain.on('terminal-write', (event, { tabId, data }) => {
ptyProcess.write(data);
});
ipcMain.on('terminal-resize', (event, { tabId, cols, rows }) => {
ptyProcess.resize(cols, rows);
});
Audio recording workflow
// Request microphone access
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
});
// Record audio
const mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm' });
const chunks: Blob[] = [];
mediaRecorder.ondataavailable = (e) => chunks.push(e.data);
mediaRecorder.onstop = async () => {
const blob = new Blob(chunks, { type: 'audio/webm' });
const base64 = await blobToBase64(blob);
// Send to transcription API
};
mediaRecorder.start();
// Later: mediaRecorder.stop();
WebRTC patterns (PeerJS)
import Peer from 'peerjs';
const peer = new Peer(userId, {
host: 'peerjs-server.com',
port: 443,
secure: true
});
// Answer incoming calls
peer.on('call', (call) => {
call.answer(localStream);
call.on('stream', (remoteStream) => {
audioElement.srcObject = remoteStream;
});
});
// Make outgoing calls
const call = peer.call(remoteUserId, localStream);
call.on('stream', (remoteStream) => {
audioElement.srcObject = remoteStream;
});
// Screen sharing via replaceTrack (no renegotiation)
const screenStream = await navigator.mediaDevices.getDisplayMedia({ video: true });
const videoTrack = screenStream.getVideoTracks()[0];
const sender = peerConnection.getSenders().find(s => s.track?.kind === 'video');
await sender.replaceTrack(videoTrack);
Build configuration (electron-builder.yml)
appId: com.yourname.appname
productName: AppName
directories:
output: release
win:
target:
- target: nsis
arch: [x64]
icon: assets/icon.ico
nsis:
oneClick: false
allowToChangeInstallationDirectory: true
installerIcon: assets/icon.ico
uninstallerIcon: assets/icon.ico
mac:
target:
- target: dmg
arch: [x64, arm64]
icon: assets/icon.icns
hardenedRuntime: true
gatekeeperAssess: false
entitlements: build/entitlements.mac.plist
entitlementsInherit: build/entitlements.mac.plist
notarize:
teamId: YOUR_APPLE_TEAM_ID
linux:
target:
- target: AppImage
arch: [x64]
icon: assets/icon.png
publish:
provider: github
owner: username
repo: repo-name
extraResources:
- from: "node_modules/node-pty/build/Release/"
to: "node-pty/"
filter: ["*.node"]
macOS notarization is required for distribution outside the App Store; Gatekeeper blocks unnotarized apps on first launch. Set the env vars APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, and APPLE_TEAM_ID (or use an App Store Connect API key) before running npm run package. electron-builder ≥ 24.13 handles notarization natively via the mac.notarize field; older versions require the electron-notarize afterSign hook.
For Windows, code signing with an EV cert is increasingly necessary to avoid SmartScreen warnings. electron-builder reads CSC_LINK (PFX) and CSC_KEY_PASSWORD env vars.
Common pitfalls
Stale closures in callbacks:
// Problem: State is stale in async callbacks
const [state, setState] = useState(initialValue);
peer.on('call', () => {
console.log(state); // Always shows initialValue
});
// Solution: Use refs for async callback access
const stateRef = useRef(state);
useEffect(() => { stateRef.current = state; }, [state]);
peer.on('call', () => {
console.log(stateRef.current); // Current value
});
Context isolation security:
- Never expose
ipcRendererdirectly to renderer - Always use
contextBridge.exposeInMainWorld() - Validate all IPC arguments in main process
- Use TypeScript interfaces for IPC contracts
BrowserView is deprecated, use WebContentsView:
BrowserView was deprecated in Electron 30 (April 2024) and the underlying implementation has been replaced. BrowserView still works as a compatibility shim over WebContentsView, but new code should target WebContentsView directly. The constructors take the same webPreferences shape, so the migration is mostly mechanical. The differences worth knowing:
WebContentsViewis added viawin.contentView.addChildView(view)instead ofwin.addBrowserView(view)- Sizing is via
view.setBounds({x, y, width, height}), nosetAutoResize. You wire your own resize handlers if you want auto-resize. - Z-order is the order of
addChildViewcalls;removeChildViewthen re-addChildViewto bring forward.
const { WebContentsView } = require('electron');
const view = new WebContentsView({
webPreferences: { contextIsolation: true, sandbox: true }
});
view.webContents.loadURL('https://example.com');
mainWindow.contentView.addChildView(view);
view.setBounds({ x: 0, y: 80, width: 800, height: 520 });
See the official BrowserView → WebContentsView migration guide for edge cases (popups, devtools, focus management).
Cross-platform shell detection:
const shell = process.platform === 'win32'
? 'powershell.exe'
: process.env.SHELL || '/bin/bash';
const shellArgs = process.platform === 'win32'
? ['-NoLogo']
: [];
Development workflow
# Development (hot reload)
npm run electron:dev
# Production build
npm run electron:build
# Run built app locally
npx electron dist/
# Package for distribution
npm run package
FAQ
What does the electron-dev skill do?
Electron desktop apps with React, TypeScript, and Vite. Use for IPC, window/tray, PTY terminals, WebRTC, and packaging.
How do I install electron-dev?
Run npx skills add https://github.com/jamditis/claude-skills-journalism --skill electron-dev in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/electron-dev/ (personal) or .claude/skills/electron-dev/ (project).
Which AI agents can use electron-dev?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is electron-dev safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains electron-dev?
It is published by jamditis in jamditis/claude-skills-journalism. It has 305 recorded installs.
More from jamditis/claude-skills-journalism
web-scraping
jamditis/claude-skills-journalism
Authorized web scraping with fallback cascades and access-failure handling. Use for social media, yt-dlp, CAPTCHA or 403 blocks.
academic-writing
jamditis/claude-skills-journalism
Scholarly writing and research compliance. Use for CRediT, preregistration, Plan S, Nelson Memo, preprints, ORCID, LLM disclosure.
page-monitoring
jamditis/claude-skills-journalism
Web page change detection, availability tracking, and RSS feed generation. Use to monitor changes, downtime, or make a feed.
fact-check-workflow
jamditis/claude-skills-journalism
Structured fact-checking workflow. Use when verifying claims for publication, rating claims, or setting pre-publication checks.
pdf-design
jamditis/claude-skills-journalism
Designs PDF reports and proposals from HTML with previews and branding. Use to create, export, or securely upload a PDF.
source-verification
jamditis/claude-skills-journalism
Open the skill to read its SKILL.md instructions.
Popular frontend & design skills
setup-matt-pocock-skills
mattpocock/skills
Configure this repo for the engineering skills: set up its issue tracker, triage label vocabulary, and domain doc layout. Run once before first use of the other engineering skills.
frontend-design
anthropics/skills
Guidance for distinctive, intentional visual design when building new UI or reshaping an existing one. Helps with aesthetic direction, typography, and making choices that don't read as templated defaults.
hyperframes-animation
heygen-com/hyperframes
All animation knowledge for HyperFrames — atomic motion rules, multi-phase scene blueprints, scene transitions, broader motion-design techniques, AND the seven runtime adapters (GSAP default, plus Lottie, Three.js, Anime.js, CSS keyframes, Web Animations API, TypeGPU). Use for any motion or animation task: pick 2-4 rules and compose, or load a blueprint, or look up runtime-specific API (e.g. GSAP eases / Lottie player / Three.js mixer). Also covers auditing an existing composition's choreography (animation map) and 24 named text-animation effects. HyperFrames-native: single paused timeline, seek-safe, deterministic.
prototype
mattpocock/skills
Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like.
hyperframes-core
heygen-com/hyperframes
The HyperFrames composition contract — build one renderable project. Use for composition structure, the data- timing attributes, class="clip", tracks, sub-compositions, variables, framework-owned media playback, deterministic-render rules, and validation. Read before writing composition HTML.
vercel-react-best-practices
vercel-labs/agent-skills
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.