Fantasia Release Build
vishiri/fantasia-archive
Produces production Electron builds for Fantasia Archive with electron- builder and Quasar.
Master Electron desktop app development with secure IPC, contextIsolation, preload scripts, multi-process architecture, electron-builder packaging, code signing, and auto-update.
$ npx skills add davila7/claude-code-templates --skill electron-development -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install davila7/claude-code-templates electron-development --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/davila7/claude-code-templates.git skills-src && mkdir -p .claude/skills && cp -r skills-src/cli-tool/components/skills/web-development/electron-development .claude/skills/electron-development && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "electron-development" agent skill from https://github.com/davila7/claude-code-templates/tree/main/cli-tool/components/skills/web-development/electron-development into .claude/skills/electron-development/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "electron-development", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/davila7/claude-code-templates/tree/main/cli-tool/components/skills/web-development/electron-developmentType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add davila7/claude-code-templates --skill electron-development -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install davila7/claude-code-templates electron-development --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/davila7/claude-code-templates.git skills-src && mkdir -p .agents/skills && cp -r skills-src/cli-tool/components/skills/web-development/electron-development .agents/skills/electron-development && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "electron-development" agent skill from https://github.com/davila7/claude-code-templates/tree/main/cli-tool/components/skills/web-development/electron-development into .agents/skills/electron-development/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "electron-development", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add davila7/claude-code-templates --skill electron-development -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install davila7/claude-code-templates electron-development --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/davila7/claude-code-templates.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/cli-tool/components/skills/web-development/electron-development .cursor/skills/electron-development && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "electron-development" agent skill from https://github.com/davila7/claude-code-templates/tree/main/cli-tool/components/skills/web-development/electron-development into .cursor/skills/electron-development/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "electron-development", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/davila7/claude-code-templates.git --path cli-tool/components/skills/web-development/electron-development--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add davila7/claude-code-templates --skill electron-development -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install davila7/claude-code-templates electron-development --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/davila7/claude-code-templates.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/cli-tool/components/skills/web-development/electron-development .gemini/skills/electron-development && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "electron-development" agent skill from https://github.com/davila7/claude-code-templates/tree/main/cli-tool/components/skills/web-development/electron-development into .gemini/skills/electron-development/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "electron-development", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install davila7/claude-code-templates electron-developmentInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add davila7/claude-code-templates --skill electron-development -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/davila7/claude-code-templates.git skills-src && mkdir -p .github/skills && cp -r skills-src/cli-tool/components/skills/web-development/electron-development .github/skills/electron-development && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "electron-development" agent skill from https://github.com/davila7/claude-code-templates/tree/main/cli-tool/components/skills/web-development/electron-development into .github/skills/electron-development/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "electron-development", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add davila7/claude-code-templates --skill electron-development -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install davila7/claude-code-templates electron-development --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/davila7/claude-code-templates.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/cli-tool/components/skills/web-development/electron-development .opencode/skills/electron-development && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "electron-development" agent skill from https://github.com/davila7/claude-code-templates/tree/main/cli-tool/components/skills/web-development/electron-development into .opencode/skills/electron-development/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "electron-development", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
electron-developmentMaster Electron desktop app development with secure IPC, contextIsolation, preload scripts, multi-process architecture, electron-builder packaging, code signing, and auto-update.
Electron Development is an agent skill from davila7/claude-code-templates. Master Electron desktop app development with secure IPC, contextIsolation, preload scripts, multi-process architecture, electron-builder packaging, code signing, and auto-update.
Its SKILL.md is about 7k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Mobile, covering App store release. It works with Electron. The repository describes itself as: CLI tool for configuring and monitoring Claude Code. The licence is MIT.
7 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 4c82aba. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
npxFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use npx, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
CSC_KEY_PASSWORDWIN_CSC_KEY_PASSWORDFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Electron Development loads about 7k tokens when it runs. Until then it costs about 50 tokens; SKILL.md has 1,164 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from davila7/claude-code-templates at commit 4c82aba, republished under its MIT licence (© davila7). 1,164 words, ~6,984 tokens.
.claude/skills/electron-development/SKILL.md (or your agent's skills folder).You are a senior Electron engineer specializing in secure, production-grade desktop application architecture. You have deep expertise in Electron's multi-process model, IPC security patterns, native OS integration, application packaging, code signing, and auto-update strategies.
react-patterns, nextjs-best-practicestauri-development if availablechrome-extension-developernodejs-backend-patternsreact-native-architecture or flutter-expertcontextIsolation: true, nodeIntegration: false, sandbox: true.Recommended project layout:
my-electron-app/
├── package.json
├── electron-builder.yml # or forge.config.ts
├── src/
│ ├── main/
│ │ ├── main.ts # Main process entry
│ │ ├── ipc-handlers.ts # IPC channel handlers
│ │ ├── menu.ts # Application menu
│ │ ├── tray.ts # System tray
│ │ └── updater.ts # Auto-update logic
│ ├── preload/
│ │ └── preload.ts # Bridge between main ↔ renderer
│ ├── renderer/
│ │ ├── index.html # Entry HTML
│ │ ├── App.tsx # UI root (React/Vue/Svelte/vanilla)
│ │ ├── components/
│ │ └── styles/
│ └── shared/
│ ├── constants.ts # IPC channel names, shared enums
│ └── types.ts # Shared TypeScript interfaces
├── resources/
│ ├── icon.png # App icon (1024x1024)
│ └── entitlements.mac.plist # macOS entitlements
├── tests/
│ ├── unit/
│ └── e2e/
└── tsconfig.jsonKey architectural principles:
shared/ directory contains only types, constants, and enums — never executable code imported across process boundaries.Electron runs multiple processes that are isolated by design:
| Process | Role | Node.js Access | DOM Access |
|---|---|---|---|
| Main | App lifecycle, windows, native APIs, IPC hub | ✅ Full | ❌ None |
| Renderer | UI rendering, user interaction | ❌ None (by default) | ✅ Full |
| Preload | Secure bridge between main and renderer | ✅ Limited (via contextBridge) | ✅ Before page loads |
| Utility | CPU-intensive tasks, background work | ✅ Full | ❌ None |
BrowserWindow with security defaults (MANDATORY):
import { BrowserWindow } from 'electron';
import path from 'node:path';
function createMainWindow(): BrowserWindow {
const win = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
// ── SECURITY DEFAULTS (NEVER CHANGE THESE) ──
contextIsolation: true, // Isolates preload from renderer context
nodeIntegration: false, // Prevents require() in renderer
sandbox: true, // OS-level process sandboxing
// ── PRELOAD SCRIPT ──
preload: path.join(__dirname, '../preload/preload.js'),
// ── ADDITIONAL HARDENING ──
webSecurity: true, // Enforce same-origin policy
allowRunningInsecureContent: false,
experimentalFeatures: false,
},
});
// Content Security Policy
win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;"
],
},
});
});
return win;
}⚠️ CRITICAL: Never set
nodeIntegration: trueorcontextIsolation: falsein production. These settings expose the renderer to remote code execution (RCE) attacks through XSS vulnerabilities.
IPC is the only safe channel for communication between main and renderer processes. All IPC must flow through the preload script.
Preload script (contextBridge + explicit whitelisting):
// src/preload/preload.ts
import { contextBridge, ipcRenderer } from 'electron';
// ── WHITELIST: Only expose specific channels ──
const ALLOWED_SEND_CHANNELS = [
'file:save',
'file:open',
'app:get-version',
'dialog:show-open',
] as const;
const ALLOWED_RECEIVE_CHANNELS = [
'file:saved',
'file:opened',
'app:version',
'update:available',
'update:progress',
'update:downloaded',
'update:error',
] as const;
type SendChannel = typeof ALLOWED_SEND_CHANNELS[number];
type ReceiveChannel = typeof ALLOWED_RECEIVE_CHANNELS[number];
contextBridge.exposeInMainWorld('electronAPI', {
// One-way: renderer → main
send: (channel: SendChannel, ...args: unknown[]) => {
if (ALLOWED_SEND_CHANNELS.includes(channel)) {
ipcRenderer.send(channel, ...args);
}
},
// Two-way: renderer → main → renderer (request/response)
invoke: (channel: SendChannel, ...args: unknown[]) => {
if (ALLOWED_SEND_CHANNELS.includes(channel)) {
return ipcRenderer.invoke(channel, ...args);
}
return Promise.reject(new Error(`Channel "${channel}" is not allowed`));
},
// One-way: main → renderer (subscriptions)
on: (channel: ReceiveChannel, callback: (...args: unknown[]) => void) => {
if (ALLOWED_RECEIVE_CHANNELS.includes(channel)) {
const listener = (_event: Electron.IpcRendererEvent, ...args: unknown[]) => callback(...args);
ipcRenderer.on(channel, listener);
return () => ipcRenderer.removeListener(channel, listener);
}
return () => {};
},
});Main process IPC handlers:
// src/main/ipc-handlers.ts
import { ipcMain, dialog, BrowserWindow } from 'electron';
import { readFile, writeFile } from 'node:fs/promises';
export function registerIpcHandlers(): void {
// invoke() pattern: returns a value to the renderer
ipcMain.handle('file:open', async () => {
const { canceled, filePaths } = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [{ name: 'Text Files', extensions: ['txt', 'md'] }],
});
if (canceled || filePaths.length === 0) return null;
const content = await readFile(filePaths[0], 'utf-8');
return { path: filePaths[0], content };
});
ipcMain.handle('file:save', async (_event, filePath: string, content: string) => {
// VALIDATE INPUTS — never trust renderer data blindly
if (typeof filePath !== 'string' || typeof content !== 'string') {
throw new Error('Invalid arguments');
}
await writeFile(filePath, content, 'utf-8');
return { success: true };
});
ipcMain.handle('app:get-version', () => {
return process.versions.electron;
});
}Renderer usage (type-safe):
// src/renderer/App.tsx — or any renderer code
// The electronAPI is globally available via contextBridge
declare global {
interface Window {
electronAPI: {
send: (channel: string, ...args: unknown[]) => void;
invoke: (channel: string, ...args: unknown[]) => Promise<unknown>;
on: (channel: string, callback: (...args: unknown[]) => void) => () => void;
};
}
}
// Open a file via IPC
async function openFile() {
const result = await window.electronAPI.invoke('file:open');
if (result) {
console.log('File content:', result.content);
}
}
// Subscribe to updates from main process
const unsubscribe = window.electronAPI.on('update:available', (version) => {
console.log('Update available:', version);
});
// Cleanup on unmount
// unsubscribe();IPC Pattern Summary:
| Pattern | Method | Use Case |
|---|---|---|
| Fire-and-forget | ipcRenderer.send() → ipcMain.on() | Logging, telemetry, non-critical notifications |
| Request/Response | ipcRenderer.invoke() → ipcMain.handle() | File operations, dialogs, data queries |
| Push to renderer | webContents.send() → ipcRenderer.on() | Progress updates, download status, auto-update |
⚠️ Never use
ipcRenderer.sendSync()in production — it blocks the renderer's event loop and freezes the UI.
── MANDATORY ──
[ ] contextIsolation: true
[ ] nodeIntegration: false
[ ] sandbox: true
[ ] webSecurity: true
[ ] allowRunningInsecureContent: false
── IPC ──
[ ] Preload uses contextBridge with explicit channel whitelisting
[ ] All IPC inputs are validated in the main process
[ ] No raw ipcRenderer exposed to renderer context
[ ] No use of ipcRenderer.sendSync()
── CONTENT ──
[ ] Content Security Policy (CSP) headers set on all windows
[ ] No use of eval(), new Function(), or innerHTML with untrusted data
[ ] Remote content (if any) loaded in separate BrowserView with restricted permissions
[ ] protocol.registerSchemesAsPrivileged() uses minimal permissions
── NAVIGATION ──
[ ] webContents 'will-navigate' event intercepted — block unexpected URLs
[ ] webContents 'new-window' event intercepted — prevent pop-up exploitation
[ ] No shell.openExternal() with unsanitized URLs
── PACKAGING ──
[ ] ASAR archive enabled (protects source from casual inspection)
[ ] No sensitive credentials or API keys bundled in the app
[ ] Code signing configured for both Windows and macOS
[ ] Auto-update uses HTTPS and verifies signaturesPreventing Navigation Hijacking:
// In main process, after creating a BrowserWindow
win.webContents.on('will-navigate', (event, url) => {
const parsedUrl = new URL(url);
// Only allow navigation within your app
if (parsedUrl.origin !== 'http://localhost:5173') { // dev server
event.preventDefault();
console.warn(`Blocked navigation to: ${url}`);
}
});
// Prevent new windows from being opened
win.webContents.setWindowOpenHandler(({ url }) => {
try {
const externalUrl = new URL(url);
const allowedHosts = new Set(['example.com', 'docs.example.com']);
// Never forward raw renderer-controlled URLs to the OS.
// Unvalidated links can enable phishing or abuse platform URL handlers.
if (externalUrl.protocol === 'https:' && allowedHosts.has(externalUrl.hostname)) {
require('electron').shell.openExternal(externalUrl.toString());
} else {
console.warn(`Blocked external URL: ${url}`);
}
} catch {
console.warn(`Rejected invalid external URL: ${url}`);
}
return { action: 'deny' }; // Block all new Electron windows
});Custom Protocol Registration (secure):
import { protocol } from 'electron';
import path from 'node:path';
import { readFile } from 'node:fs/promises';
import { URL } from 'node:url';
// Register a custom protocol for loading local assets securely
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { standard: true, secure: true, supportFetchAPI: true } },
]);
app.whenReady().then(() => {
protocol.handle('app', async (request) => {
const url = new URL(request.url);
const baseDir = path.resolve(__dirname, '../renderer');
// Strip the leading slash so path.resolve keeps baseDir as the root.
const relativePath = path.normalize(decodeURIComponent(url.pathname).replace(/^[/\\]+/, ''));
const filePath = path.resolve(baseDir, relativePath);
if (!filePath.startsWith(baseDir)) {
return new Response('Forbidden', { status: 403 });
}
const data = await readFile(filePath);
return new Response(data);
});
});Strategy 1: Main process as single source of truth (recommended for most apps)
// src/main/store.ts
import { app } from 'electron';
import { readFileSync, writeFileSync } from 'node:fs';
import path from 'node:path';
interface AppState {
theme: 'light' | 'dark';
recentFiles: string[];
windowBounds: { x: number; y: number; width: number; height: number };
}
const DEFAULTS: AppState = {
theme: 'light',
recentFiles: [],
windowBounds: { x: 0, y: 0, width: 1200, height: 800 },
};
class Store {
private data: AppState;
private filePath: string;
constructor() {
this.filePath = path.join(app.getPath('userData'), 'settings.json');
this.data = this.load();
}
private load(): AppState {
try {
const raw = readFileSync(this.filePath, 'utf-8');
return { ...DEFAULTS, ...JSON.parse(raw) };
} catch {
return { ...DEFAULTS };
}
}
get<K extends keyof AppState>(key: K): AppState[K] {
return this.data[key];
}
set<K extends keyof AppState>(key: K, value: AppState[K]): void {
this.data[key] = value;
writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
}
}
export const store = new Store();Strategy 2: electron-store (lightweight persistent storage)
import Store from 'electron-store';
const store = new Store({
schema: {
theme: { type: 'string', enum: ['light', 'dark'], default: 'light' },
windowBounds: {
type: 'object',
properties: {
width: { type: 'number', default: 1200 },
height: { type: 'number', default: 800 },
},
},
},
});
// Usage
store.set('theme', 'dark');
console.log(store.get('theme')); // 'dark'Multi-window state synchronization:
// Main process: broadcast state changes to all windows
import { BrowserWindow } from 'electron';
function broadcastToAllWindows(channel: string, data: unknown): void {
for (const win of BrowserWindow.getAllWindows()) {
if (!win.isDestroyed()) {
win.webContents.send(channel, data);
}
}
}
// When theme changes:
ipcMain.handle('settings:set-theme', (_event, theme: 'light' | 'dark') => {
store.set('theme', theme);
broadcastToAllWindows('settings:theme-changed', theme);
});# electron-builder.yml
appId: com.mycompany.myapp
productName: My App
directories:
output: dist
buildResources: resources
files:
- "out/**/*" # compiled main + preload
- "renderer/**/*" # built renderer assets
- "package.json"
asar: true
compression: maximum
# ── macOS ──
mac:
category: public.app-category.developer-tools
hardenedRuntime: true
gatekeeperAssess: false
entitlements: resources/entitlements.mac.plist
entitlementsInherit: resources/entitlements.mac.plist
target:
- target: dmg
arch: [x64, arm64]
- target: zip
arch: [x64, arm64]
# ── Windows ──
win:
target:
- target: nsis
arch: [x64, arm64]
signingHashAlgorithms: [sha256]
nsis:
oneClick: false
allowToChangeInstallationDirectory: true
perMachine: false
# ── Linux ──
linux:
target:
- target: AppImage
- target: deb
category: Development
maintainer: your-email@example.com
# ── Auto Update ──
publish:
provider: github
owner: your-org
repo: your-repo# macOS: requires Apple Developer certificate
# Set environment variables before building:
export CSC_LINK="path/to/Developer_ID_Application.p12"
export CSC_KEY_PASSWORD="your-password"
# Windows: requires EV or standard code signing certificate
# Set environment variables:
export WIN_CSC_LINK="path/to/code-signing.pfx"
export WIN_CSC_KEY_PASSWORD="your-password"
# Build signed app
npx electron-builder --mac --win --publish never// src/main/updater.ts
import { autoUpdater } from 'electron-updater';
import { BrowserWindow } from 'electron';
import log from 'electron-log';
export function setupAutoUpdater(mainWindow: BrowserWindow): void {
autoUpdater.logger = log;
autoUpdater.autoDownload = false; // Let user decide
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.on('update-available', (info) => {
mainWindow.webContents.send('update:available', {
version: info.version,
releaseNotes: info.releaseNotes,
});
});
autoUpdater.on('download-progress', (progress) => {
mainWindow.webContents.send('update:progress', {
percent: Math.round(progress.percent),
bytesPerSecond: progress.bytesPerSecond,
});
});
autoUpdater.on('update-downloaded', () => {
mainWindow.webContents.send('update:downloaded');
});
autoUpdater.on('error', (err) => {
log.error('Update error:', err);
mainWindow.webContents.send('update:error', err.message);
});
// Check for updates every 4 hours
setInterval(() => autoUpdater.checkForUpdates(), 4 * 60 * 60 * 1000);
autoUpdater.checkForUpdates();
}
// Expose to renderer via IPC
ipcMain.handle('update:download', () => autoUpdater.downloadUpdate());
ipcMain.handle('update:install', () => autoUpdater.quitAndInstall());asar: true to package sources into a single archivecompression: maximum in electron-builder config"files" pattern should only include compiled outputnode_modules shipped with the app — use electron-builder's files exclude patterns@electron/rebuild for native modules instead of shipping prebuilt for all platformsnode_modules — only production dependencies// package.json scripts
{
"scripts": {
"dev": "concurrently \"npm run dev:renderer\" \"npm run dev:main\"",
"dev:renderer": "vite",
"dev:main": "electron-vite dev",
"build": "electron-vite build",
"start": "electron ."
}
}Recommended toolchain:
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"args": [".", "--remote-debugging-port=9223"],
"sourceMaps": true,
"outFiles": ["${workspaceFolder}/out/**/*.js"],
"env": {
"NODE_ENV": "development"
}
}
]
}Other debugging techniques:
// Enable DevTools only in development
if (process.env.NODE_ENV === 'development') {
win.webContents.openDevTools({ mode: 'detach' });
}
// Inspect specific renderer processes from command line:
// electron . --inspect=5858 --remote-debugging-port=9223Unit testing (Vitest / Jest):
// tests/unit/store.test.ts
import { describe, it, expect, vi } from 'vitest';
// Mock Electron modules for unit tests
vi.mock('electron', () => ({
app: { getPath: () => '/tmp/test' },
}));
describe('Store', () => {
it('returns default values for missing keys', () => {
// Test store logic without Electron runtime
});
});E2E testing (Playwright + Electron):
// tests/e2e/app.spec.ts
import { test, expect, _electron as electron } from '@playwright/test';
test('app launches and shows main window', async () => {
const app = await electron.launch({ args: ['.'] });
const window = await app.firstWindow();
// Wait for the app to fully load
await window.waitForLoadState('domcontentloaded');
const title = await window.title();
expect(title).toBe('My App');
// Take a screenshot for visual regression
await window.screenshot({ path: 'tests/screenshots/main-window.png' });
await app.close();
});
test('file open dialog works via IPC', async () => {
const app = await electron.launch({ args: ['.'] });
const window = await app.firstWindow();
// Test IPC by evaluating in the renderer context
const version = await window.evaluate(async () => {
return window.electronAPI.invoke('app:get-version');
});
expect(version).toBeTruthy();
await app.close();
});Playwright config for Electron:
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
timeout: 30_000,
retries: 1,
use: {
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
});// src/main/main.ts
import { app, BrowserWindow } from 'electron';
import { registerIpcHandlers } from './ipc-handlers';
import { setupAutoUpdater } from './updater';
import { store } from './store';
let mainWindow: BrowserWindow | null = null;
app.whenReady().then(() => {
registerIpcHandlers();
mainWindow = createMainWindow();
// Restore window bounds
const bounds = store.get('windowBounds');
if (bounds) mainWindow.setBounds(bounds);
// Save window bounds on close
mainWindow.on('close', () => {
if (mainWindow) store.set('windowBounds', mainWindow.getBounds());
});
// Auto-update (only in production)
if (app.isPackaged) {
setupAutoUpdater(mainWindow);
}
// macOS: re-create window when dock icon is clicked
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
mainWindow = createMainWindow();
}
});
});
// Quit when all windows are closed (except on macOS)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
// Security: prevent additional renderers from being created
app.on('web-contents-created', (_event, contents) => {
contents.on('will-attach-webview', (event) => {
event.preventDefault(); // Block <webview> tags
});
});Symptoms: App starts but renderer shows a blank/white page
Root causes: Incorrect loadFile/loadURL path, build output missing, CSP blocking scripts
Solutions: Verify the path passed to win.loadFile() or win.loadURL() exists relative to the packaged app. Check DevTools console for CSP violations. In development, ensure the Vite/webpack dev server is running before Electron starts.
Symptoms: invoke() hangs or send() has no effect
Root causes: Channel name mismatch, preload not loaded, contextBridge not exposing the channel
Solutions: Verify channel names match exactly between preload, main, and renderer. Confirm preload path is correct in webPreferences. Check that the channel is in the whitelist array.
Symptoms: App crashes on startup with MODULE_NOT_FOUND or invalid ELF header
Root causes: Native module compiled for wrong Electron/Node ABI version
Solutions: Run npx @electron/rebuild after installing native modules. Ensure electron-builder is configured with the correct Electron version for rebuilding.
Symptoms: autoUpdater.checkForUpdates() returns nothing or errors
Root causes: Missing publish config, unsigned app (macOS), incorrect GitHub release assets
Solutions: Verify publish section in electron-builder.yml. On macOS, app must be code-signed and notarized. Ensure the GitHub release contains the -mac.zip and latest-mac.yml (or equivalent Windows files).
Symptoms: Built application is excessively large
Root causes: Dev dependencies bundled, no tree-shaking, duplicate Electron binaries
Solutions: Audit files patterns in electron-builder.yml. Use a bundler (Vite/esbuild) for the renderer. Check that devDependencies are not in dependencies. Use compression: maximum.
contextIsolation: true and nodeIntegration: falsecontextBridge in preload with an explicit channel whitelistipcMain.handle() / ipcRenderer.invoke() for request/response IPCshell.openExternal()@playwright/test's Electron support for E2E testsapp.getPath('userData'), never in the app directorynodeIntegration: true — this is the #1 Electron security vulnerabilityipcRenderer or require() to the renderer contextremote module (deprecated and insecure)ipcRenderer.sendSync() — it blocks the renderer event loopwebSecurity in productionelectron-updater has limited Linux support--inspect flag — there is no integrated debugger in Electron itselfchrome-extension-developer — When building browser extensions instead of desktop apps (shares multi-process model concepts)docker-expert — When containerizing Electron's build pipeline or CI/CDreact-patterns / react-best-practices — When using React for the renderer UItypescript-pro — When setting up advanced TypeScript configurations for multi-target buildsnodejs-backend-patterns — When the main process needs complex backend logicgithub-actions-templates — When setting up CI/CD for cross-platform Electron builds© davila7, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in cli-tool/components/skills/web-development/electron-development of davila7/claude-code-templates.
Open the folder on GitHubat commit 4c82aba
We found 12 copies of this SKILL.md (exact, near-identical or edited) in other folders, from 3 other GitHub owners. This page covers the copy in davila7/claude-code-templates, which our catalogue first saw on October 7, 2026.
Electron Development next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Electron Development this skilldavila7/claude-code-templates | 32k | 3 repos | ~7k | Automated safety check: Pass | MIT | |
| Fantasia Release Buildvishiri/fantasia-archive | 409 | — | ~558 | Automated safety check: Pass | GPL-3.0 | |
| App Store Deployromankurnovskii/BrewMate | 301 | — | ~813 | Automated safety check: Notes | MIT | |
| Electron Builderpedronauck/skills | 633 | — | ~2.7k | Automated safety check: Pass | None | |
| Electron Releasepedronauck/skills | 633 | — | ~1.6k | Automated safety check: Notes | None | |
| Electron Best Practicesjwynia/agent-skills | 165 | — | ~3.1k | Automated safety check: Pass | MIT |
vishiri/fantasia-archive
Produces production Electron builds for Fantasia Archive with electron- builder and Quasar.
romankurnovskii/BrewMate
Automates the build and deployment of the Electron app to the Mac App Store and TestFlight.
pedronauck/skills
Comprehensive guide for electron-builder (v26.x) packaging, code signing, auto-updates, and release workflows.
pedronauck/skills
Expert guide for Electron production builds, code signing, notarization, auto-updates, and release workflows.
jwynia/agent-skills
Guide AI agents through Electron app development with React including security patterns, type-safe IPC, React integration, packaging with code signing, and testing.
adamlyttleapps/claude-skill-aso-appstore-screenshots
Generate high-converting App Store screenshots by analyzing your app's codebase, discovering core benefits, and creating ASO-optimized screenshot images using Nano Banana Pro.
davila7/claude-code-templates
Runs web-grounded searches through Perplexity's Sonar models over OpenRouter for current events, recent literature and cited facts beyond the model's training cutoff.
davila7/claude-code-templates
Analyzes Neuropixels recordings from SpikeGLX or Open Ephys through preprocessing, drift correction, Kilosort4 spike sorting, quality metrics and curation.
davila7/claude-code-templates
Supplies LaTeX templates and formatting rules for journals, conferences, posters, and grant proposals, then can check a draft against them.
davila7/claude-code-templates
Analyzes a brand's existing writing to lock in a consistent voice, then builds SEO blog posts and platform-specific social content around it.
davila7/claude-code-templates
Guides corrective and preventive action (CAPA) work in a quality management system, from initiation and root cause analysis through effectiveness verification.
davila7/claude-code-templates
Senior FDA consultant and specialist for medical device companies including HIPAA compliance and requirement management.
Works with
Categories
Master Electron desktop app development with secure IPC, contextIsolation, preload scripts, multi-process architecture, electron-builder packaging, code signing, and auto-update. Electron Development is an agent skill from davila7/claude-code-templates. Master Electron desktop app development with secure IPC, contextIsolation, preload scripts, multi-process architecture, electron-builder packaging, code signing, and auto-update.
Electron Development fits situations like: tasks that involve App store release.
Run `npx skills add davila7/claude-code-templates --skill electron-development -a claude-code`. Or copy the skill folder (cli-tool/components/skills/web-development/electron-development in davila7/claude-code-templates) into .claude/skills/electron-development in your project. Claude Code loads it when a task matches its description.
Run `npx skills add davila7/claude-code-templates --skill electron-development -a codex`. Or copy the skill folder (cli-tool/components/skills/web-development/electron-development in davila7/claude-code-templates) into .agents/skills/electron-development in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add davila7/claude-code-templates --skill electron-development -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/electron-development, .gemini/skills/electron-development, .github/skills/electron-development and .opencode/skills/electron-development in your project.
Going by SKILL.md and its folder, Electron Development needs the command-line tools its instructions call (npx) and credentials named CSC_KEY_PASSWORD and WIN_CSC_KEY_PASSWORD. Our summary lists: Node.js.
SKILL.md contains no URLs. Its commands use npx, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Electron Development is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 7k tokens (SKILL.md is roughly 28k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Electron Development: Fantasia Release Build (vishiri/fantasia-archive, 409 stars), App Store Deploy (romankurnovskii/BrewMate, 301 stars), Electron Builder (pedronauck/skills, 633 stars) and Electron Release (pedronauck/skills, 633 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
davila7 (a GitHub user) maintains it in davila7/claude-code-templates, which has 32,432 GitHub stars. The repository holds 477 skills in this directory. The repository was last updated on October 7, 2026.
Source: davila7/claude-code-templates on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.