{"id":"b501a5da-2d11-458d-be6d-f1cd4a33a674","shortId":"ZTnbBx","kind":"skill","title":"electron-development","tagline":"Master Electron desktop app development with secure IPC, contextIsolation, preload scripts, multi-process architecture, electron-builder packaging, code signing, and auto-update.","description":"# Electron Development\n\nYou 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.\n\n## Use this skill when\n\n- Building new Electron desktop applications from scratch\n- Securing an Electron app (contextIsolation, sandbox, CSP, nodeIntegration)\n- Setting up IPC communication between main, renderer, and preload processes\n- Packaging and distributing Electron apps with electron-builder or electron-forge\n- Implementing auto-update with electron-updater\n- Debugging main process issues or renderer crashes\n- Managing multiple windows and application lifecycle\n- Integrating native OS features (menus, tray, notifications, file system dialogs)\n- Optimizing Electron app performance and bundle size\n\n## Do not use this skill when\n\n- Building web-only applications without desktop distribution → use `react-patterns`, `nextjs-best-practices`\n- Building Tauri apps (Rust-based desktop alternative) → use `tauri-development` if available\n- Building Chrome extensions → use `chrome-extension-developer`\n- Implementing deep backend/server logic → use `nodejs-backend-patterns`\n- Building mobile apps → use `react-native-architecture` or `flutter-expert`\n\n## Instructions\n\n1. Analyze the project structure and identify process boundaries.\n2. Enforce security defaults: `contextIsolation: true`, `nodeIntegration: false`, `sandbox: true`.\n3. Design IPC channels with explicit whitelisting in the preload script.\n4. Implement, test, and build with appropriate tooling.\n5. Validate against the Production Security Checklist before shipping.\n\n---\n\n## Core Expertise Areas\n\n### 1. Project Structure & Architecture\n\n**Recommended project layout:**\n```\nmy-electron-app/\n├── package.json\n├── electron-builder.yml        # or forge.config.ts\n├── src/\n│   ├── main/\n│   │   ├── main.ts             # Main process entry\n│   │   ├── ipc-handlers.ts     # IPC channel handlers\n│   │   ├── menu.ts             # Application menu\n│   │   ├── tray.ts             # System tray\n│   │   └── updater.ts          # Auto-update logic\n│   ├── preload/\n│   │   └── preload.ts          # Bridge between main ↔ renderer\n│   ├── renderer/\n│   │   ├── index.html          # Entry HTML\n│   │   ├── App.tsx             # UI root (React/Vue/Svelte/vanilla)\n│   │   ├── components/\n│   │   └── styles/\n│   └── shared/\n│       ├── constants.ts        # IPC channel names, shared enums\n│       └── types.ts            # Shared TypeScript interfaces\n├── resources/\n│   ├── icon.png                # App icon (1024x1024)\n│   └── entitlements.mac.plist  # macOS entitlements\n├── tests/\n│   ├── unit/\n│   └── e2e/\n└── tsconfig.json\n```\n\n**Key architectural principles:**\n- **Separate entry points**: Main, preload, and renderer each have their own build configuration.\n- **Shared types, not shared modules**: The `shared/` directory contains only types, constants, and enums — never executable code imported across process boundaries.\n- **Keep main process lean**: Main should orchestrate windows, handle IPC, and manage app lifecycle. Business logic belongs in the renderer or dedicated worker processes.\n\n---\n\n### 2. Process Model (Main / Renderer / Preload / Utility)\n\nElectron runs **multiple processes** that are isolated by design:\n\n| Process | Role | Node.js Access | DOM Access |\n|---------|------|----------------|------------|\n| **Main** | App lifecycle, windows, native APIs, IPC hub | ✅ Full | ❌ None |\n| **Renderer** | UI rendering, user interaction | ❌ None (by default) | ✅ Full |\n| **Preload** | Secure bridge between main and renderer | ✅ Limited (via contextBridge) | ✅ Before page loads |\n| **Utility** | CPU-intensive tasks, background work | ✅ Full | ❌ None |\n\n**BrowserWindow with security defaults (MANDATORY):**\n```typescript\nimport { BrowserWindow } from 'electron';\nimport path from 'node:path';\n\nfunction createMainWindow(): BrowserWindow {\n  const win = new BrowserWindow({\n    width: 1200,\n    height: 800,\n    webPreferences: {\n      // ── SECURITY DEFAULTS (NEVER CHANGE THESE) ──\n      contextIsolation: true,     // Isolates preload from renderer context\n      nodeIntegration: false,     // Prevents require() in renderer\n      sandbox: true,              // OS-level process sandboxing\n      \n      // ── PRELOAD SCRIPT ──\n      preload: path.join(__dirname, '../preload/preload.js'),\n      \n      // ── ADDITIONAL HARDENING ──\n      webSecurity: true,          // Enforce same-origin policy\n      allowRunningInsecureContent: false,\n      experimentalFeatures: false,\n    },\n  });\n\n  // Content Security Policy\n  win.webContents.session.webRequest.onHeadersReceived((details, callback) => {\n    callback({\n      responseHeaders: {\n        ...details.responseHeaders,\n        'Content-Security-Policy': [\n          \"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;\"\n        ],\n      },\n    });\n  });\n\n  return win;\n}\n```\n\n> ⚠️ **CRITICAL**: Never set `nodeIntegration: true` or `contextIsolation: false` in production. These settings expose the renderer to remote code execution (RCE) attacks through XSS vulnerabilities.\n\n---\n\n### 3. Secure IPC Communication\n\nIPC is the **only** safe channel for communication between main and renderer processes. All IPC must flow through the preload script.\n\n**Preload script (contextBridge + explicit whitelisting):**\n```typescript\n// src/preload/preload.ts\nimport { contextBridge, ipcRenderer } from 'electron';\n\n// ── WHITELIST: Only expose specific channels ──\nconst ALLOWED_SEND_CHANNELS = [\n  'file:save',\n  'file:open',\n  'app:get-version',\n  'dialog:show-open',\n] as const;\n\nconst ALLOWED_RECEIVE_CHANNELS = [\n  'file:saved',\n  'file:opened',\n  'app:version',\n  'update:available',\n  'update:progress',\n  'update:downloaded',\n  'update:error',\n] as const;\n\ntype SendChannel = typeof ALLOWED_SEND_CHANNELS[number];\ntype ReceiveChannel = typeof ALLOWED_RECEIVE_CHANNELS[number];\n\ncontextBridge.exposeInMainWorld('electronAPI', {\n  // One-way: renderer → main\n  send: (channel: SendChannel, ...args: unknown[]) => {\n    if (ALLOWED_SEND_CHANNELS.includes(channel)) {\n      ipcRenderer.send(channel, ...args);\n    }\n  },\n\n  // Two-way: renderer → main → renderer (request/response)\n  invoke: (channel: SendChannel, ...args: unknown[]) => {\n    if (ALLOWED_SEND_CHANNELS.includes(channel)) {\n      return ipcRenderer.invoke(channel, ...args);\n    }\n    return Promise.reject(new Error(`Channel \"${channel}\" is not allowed`));\n  },\n\n  // One-way: main → renderer (subscriptions)\n  on: (channel: ReceiveChannel, callback: (...args: unknown[]) => void) => {\n    if (ALLOWED_RECEIVE_CHANNELS.includes(channel)) {\n      const listener = (_event: Electron.IpcRendererEvent, ...args: unknown[]) => callback(...args);\n      ipcRenderer.on(channel, listener);\n      return () => ipcRenderer.removeListener(channel, listener);\n    }\n    return () => {};\n  },\n});\n```\n\n**Main process IPC handlers:**\n```typescript\n// src/main/ipc-handlers.ts\nimport { ipcMain, dialog, BrowserWindow } from 'electron';\nimport { readFile, writeFile } from 'node:fs/promises';\n\nexport function registerIpcHandlers(): void {\n  // invoke() pattern: returns a value to the renderer\n  ipcMain.handle('file:open', async () => {\n    const { canceled, filePaths } = await dialog.showOpenDialog({\n      properties: ['openFile'],\n      filters: [{ name: 'Text Files', extensions: ['txt', 'md'] }],\n    });\n    \n    if (canceled || filePaths.length === 0) return null;\n    \n    const content = await readFile(filePaths[0], 'utf-8');\n    return { path: filePaths[0], content };\n  });\n\n  ipcMain.handle('file:save', async (_event, filePath: string, content: string) => {\n    // VALIDATE INPUTS — never trust renderer data blindly\n    if (typeof filePath !== 'string' || typeof content !== 'string') {\n      throw new Error('Invalid arguments');\n    }\n    await writeFile(filePath, content, 'utf-8');\n    return { success: true };\n  });\n\n  ipcMain.handle('app:get-version', () => {\n    return process.versions.electron;\n  });\n}\n```\n\n**Renderer usage (type-safe):**\n```typescript\n// src/renderer/App.tsx — or any renderer code\n// The electronAPI is globally available via contextBridge\n\ndeclare global {\n  interface Window {\n    electronAPI: {\n      send: (channel: string, ...args: unknown[]) => void;\n      invoke: (channel: string, ...args: unknown[]) => Promise<unknown>;\n      on: (channel: string, callback: (...args: unknown[]) => void) => () => void;\n    };\n  }\n}\n\n// Open a file via IPC\nasync function openFile() {\n  const result = await window.electronAPI.invoke('file:open');\n  if (result) {\n    console.log('File content:', result.content);\n  }\n}\n\n// Subscribe to updates from main process\nconst unsubscribe = window.electronAPI.on('update:available', (version) => {\n  console.log('Update available:', version);\n});\n\n// Cleanup on unmount\n// unsubscribe();\n```\n\n**IPC Pattern Summary:**\n\n| Pattern | Method | Use Case |\n|---------|--------|----------|\n| **Fire-and-forget** | `ipcRenderer.send()` → `ipcMain.on()` | Logging, telemetry, non-critical notifications |\n| **Request/Response** | `ipcRenderer.invoke()` → `ipcMain.handle()` | File operations, dialogs, data queries |\n| **Push to renderer** | `webContents.send()` → `ipcRenderer.on()` | Progress updates, download status, auto-update |\n\n> ⚠️ **Never** use `ipcRenderer.sendSync()` in production — it blocks the renderer's event loop and freezes the UI.\n\n---\n\n### 4. Security Hardening\n\n#### Production Security Checklist\n\n```\n── MANDATORY ──\n[ ] contextIsolation: true\n[ ] nodeIntegration: false\n[ ] sandbox: true\n[ ] webSecurity: true\n[ ] allowRunningInsecureContent: false\n\n── IPC ──\n[ ] Preload uses contextBridge with explicit channel whitelisting\n[ ] All IPC inputs are validated in the main process\n[ ] No raw ipcRenderer exposed to renderer context\n[ ] No use of ipcRenderer.sendSync()\n\n── CONTENT ──\n[ ] Content Security Policy (CSP) headers set on all windows\n[ ] No use of eval(), new Function(), or innerHTML with untrusted data\n[ ] Remote content (if any) loaded in separate BrowserView with restricted permissions\n[ ] protocol.registerSchemesAsPrivileged() uses minimal permissions\n\n── NAVIGATION ──\n[ ] webContents 'will-navigate' event intercepted — block unexpected URLs\n[ ] webContents 'new-window' event intercepted — prevent pop-up exploitation\n[ ] No shell.openExternal() with unsanitized URLs\n\n── PACKAGING ──\n[ ] ASAR archive enabled (protects source from casual inspection)\n[ ] No sensitive credentials or API keys bundled in the app\n[ ] Code signing configured for both Windows and macOS\n[ ] Auto-update uses HTTPS and verifies signatures\n```\n\n**Preventing Navigation Hijacking:**\n```typescript\n// In main process, after creating a BrowserWindow\nwin.webContents.on('will-navigate', (event, url) => {\n  const parsedUrl = new URL(url);\n  // Only allow navigation within your app\n  if (parsedUrl.origin !== 'http://localhost:5173') { // dev server\n    event.preventDefault();\n    console.warn(`Blocked navigation to: ${url}`);\n  }\n});\n\n// Prevent new windows from being opened\nwin.webContents.setWindowOpenHandler(({ url }) => {\n  try {\n    const externalUrl = new URL(url);\n    const allowedHosts = new Set(['example.com', 'docs.example.com']);\n\n    // Never forward raw renderer-controlled URLs to the OS.\n    // Unvalidated links can enable phishing or abuse platform URL handlers.\n    if (externalUrl.protocol === 'https:' && allowedHosts.has(externalUrl.hostname)) {\n      require('electron').shell.openExternal(externalUrl.toString());\n    } else {\n      console.warn(`Blocked external URL: ${url}`);\n    }\n  } catch {\n    console.warn(`Rejected invalid external URL: ${url}`);\n  }\n\n  return { action: 'deny' }; // Block all new Electron windows\n});\n```\n\n**Custom Protocol Registration (secure):**\n```typescript\nimport { protocol } from 'electron';\nimport path from 'node:path';\nimport { readFile } from 'node:fs/promises';\nimport { URL } from 'node:url';\n\n// Register a custom protocol for loading local assets securely\nprotocol.registerSchemesAsPrivileged([\n  { scheme: 'app', privileges: { standard: true, secure: true, supportFetchAPI: true } },\n]);\n\napp.whenReady().then(() => {\n  protocol.handle('app', async (request) => {\n    const url = new URL(request.url);\n    const baseDir = path.resolve(__dirname, '../renderer');\n    // Strip the leading slash so path.resolve keeps baseDir as the root.\n    const relativePath = path.normalize(decodeURIComponent(url.pathname).replace(/^[/\\\\]+/, ''));\n    const filePath = path.resolve(baseDir, relativePath);\n\n    if (!filePath.startsWith(baseDir)) {\n      return new Response('Forbidden', { status: 403 });\n    }\n\n    const data = await readFile(filePath);\n    return new Response(data);\n  });\n});\n```\n\n---\n\n### 5. State Management Across Processes\n\n**Strategy 1: Main process as single source of truth (recommended for most apps)**\n```typescript\n// src/main/store.ts\nimport { app } from 'electron';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\n\ninterface AppState {\n  theme: 'light' | 'dark';\n  recentFiles: string[];\n  windowBounds: { x: number; y: number; width: number; height: number };\n}\n\nconst DEFAULTS: AppState = {\n  theme: 'light',\n  recentFiles: [],\n  windowBounds: { x: 0, y: 0, width: 1200, height: 800 },\n};\n\nclass Store {\n  private data: AppState;\n  private filePath: string;\n\n  constructor() {\n    this.filePath = path.join(app.getPath('userData'), 'settings.json');\n    this.data = this.load();\n  }\n\n  private load(): AppState {\n    try {\n      const raw = readFileSync(this.filePath, 'utf-8');\n      return { ...DEFAULTS, ...JSON.parse(raw) };\n    } catch {\n      return { ...DEFAULTS };\n    }\n  }\n\n  get<K extends keyof AppState>(key: K): AppState[K] {\n    return this.data[key];\n  }\n\n  set<K extends keyof AppState>(key: K, value: AppState[K]): void {\n    this.data[key] = value;\n    writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));\n  }\n}\n\nexport const store = new Store();\n```\n\n**Strategy 2: electron-store (lightweight persistent storage)**\n```typescript\nimport Store from 'electron-store';\n\nconst store = new Store({\n  schema: {\n    theme: { type: 'string', enum: ['light', 'dark'], default: 'light' },\n    windowBounds: {\n      type: 'object',\n      properties: {\n        width: { type: 'number', default: 1200 },\n        height: { type: 'number', default: 800 },\n      },\n    },\n  },\n});\n\n// Usage\nstore.set('theme', 'dark');\nconsole.log(store.get('theme')); // 'dark'\n```\n\n**Multi-window state synchronization:**\n```typescript\n// Main process: broadcast state changes to all windows\nimport { BrowserWindow } from 'electron';\n\nfunction broadcastToAllWindows(channel: string, data: unknown): void {\n  for (const win of BrowserWindow.getAllWindows()) {\n    if (!win.isDestroyed()) {\n      win.webContents.send(channel, data);\n    }\n  }\n}\n\n// When theme changes:\nipcMain.handle('settings:set-theme', (_event, theme: 'light' | 'dark') => {\n  store.set('theme', theme);\n  broadcastToAllWindows('settings:theme-changed', theme);\n});\n```\n\n---\n\n### 6. Build, Signing & Distribution\n\n#### electron-builder Configuration\n\n```yaml\n# electron-builder.yml\nappId: com.mycompany.myapp\nproductName: My App\ndirectories:\n  output: dist\n  buildResources: resources\n\nfiles:\n  - \"out/**/*\"       # compiled main + preload\n  - \"renderer/**/*\"  # built renderer assets\n  - \"package.json\"\n\nasar: true\ncompression: maximum\n\n# ── macOS ──\nmac:\n  category: public.app-category.developer-tools\n  hardenedRuntime: true\n  gatekeeperAssess: false\n  entitlements: resources/entitlements.mac.plist\n  entitlementsInherit: resources/entitlements.mac.plist\n  target:\n    - target: dmg\n      arch: [x64, arm64]\n    - target: zip\n      arch: [x64, arm64]\n\n# ── Windows ──\nwin:\n  target:\n    - target: nsis\n      arch: [x64, arm64]\n  signingHashAlgorithms: [sha256]\n\nnsis:\n  oneClick: false\n  allowToChangeInstallationDirectory: true\n  perMachine: false\n\n# ── Linux ──\nlinux:\n  target:\n    - target: AppImage\n    - target: deb\n  category: Development\n  maintainer: your-email@example.com\n\n# ── Auto Update ──\npublish:\n  provider: github\n  owner: your-org\n  repo: your-repo\n```\n\n#### Code Signing\n\n```bash\n# macOS: requires Apple Developer certificate\n# Set environment variables before building:\nexport CSC_LINK=\"path/to/Developer_ID_Application.p12\"\nexport CSC_KEY_PASSWORD=\"your-password\"\n\n# Windows: requires EV or standard code signing certificate\n# Set environment variables:\nexport WIN_CSC_LINK=\"path/to/code-signing.pfx\"\nexport WIN_CSC_KEY_PASSWORD=\"your-password\"\n\n# Build signed app\nnpx electron-builder --mac --win --publish never\n```\n\n#### Auto-Update with electron-updater\n\n```typescript\n// src/main/updater.ts\nimport { autoUpdater } from 'electron-updater';\nimport { BrowserWindow } from 'electron';\nimport log from 'electron-log';\n\nexport function setupAutoUpdater(mainWindow: BrowserWindow): void {\n  autoUpdater.logger = log;\n  autoUpdater.autoDownload = false; // Let user decide\n  autoUpdater.autoInstallOnAppQuit = true;\n\n  autoUpdater.on('update-available', (info) => {\n    mainWindow.webContents.send('update:available', {\n      version: info.version,\n      releaseNotes: info.releaseNotes,\n    });\n  });\n\n  autoUpdater.on('download-progress', (progress) => {\n    mainWindow.webContents.send('update:progress', {\n      percent: Math.round(progress.percent),\n      bytesPerSecond: progress.bytesPerSecond,\n    });\n  });\n\n  autoUpdater.on('update-downloaded', () => {\n    mainWindow.webContents.send('update:downloaded');\n  });\n\n  autoUpdater.on('error', (err) => {\n    log.error('Update error:', err);\n    mainWindow.webContents.send('update:error', err.message);\n  });\n\n  // Check for updates every 4 hours\n  setInterval(() => autoUpdater.checkForUpdates(), 4 * 60 * 60 * 1000);\n  autoUpdater.checkForUpdates();\n}\n\n// Expose to renderer via IPC\nipcMain.handle('update:download', () => autoUpdater.downloadUpdate());\nipcMain.handle('update:install', () => autoUpdater.quitAndInstall());\n```\n\n#### Bundle Size Optimization\n\n- ✅ Use `asar: true` to package sources into a single archive\n- ✅ Set `compression: maximum` in electron-builder config\n- ✅ Exclude dev dependencies: `\"files\"` pattern should only include compiled output\n- ✅ Use a bundler (Vite, webpack, esbuild) to tree-shake the renderer\n- ✅ Audit `node_modules` shipped with the app — use `electron-builder`'s `files` exclude patterns\n- ✅ Consider `@electron/rebuild` for native modules instead of shipping prebuilt for all platforms\n- ❌ Do NOT bundle the entire `node_modules` — only production dependencies\n\n---\n\n### 7. Developer Experience & Debugging\n\n#### Development Setup with Hot Reload\n\n```json\n// package.json scripts\n{\n  \"scripts\": {\n    \"dev\": \"concurrently \\\"npm run dev:renderer\\\" \\\"npm run dev:main\\\"\",\n    \"dev:renderer\": \"vite\",\n    \"dev:main\": \"electron-vite dev\",\n    \"build\": \"electron-vite build\",\n    \"start\": \"electron .\"\n  }\n}\n```\n\n**Recommended toolchain:**\n- **electron-vite** or **electron-forge with Vite plugin** — modern, fast HMR for renderer\n- **tsx** or **ts-node** — for running TypeScript in main process during development\n- **concurrently** — run renderer dev server + Electron simultaneously\n\n#### Debugging the Main Process\n\n```json\n// .vscode/launch.json\n{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"name\": \"Debug Main Process\",\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"cwd\": \"${workspaceFolder}\",\n      \"runtimeExecutable\": \"${workspaceFolder}/node_modules/.bin/electron\",\n      \"args\": [\".\", \"--remote-debugging-port=9223\"],\n      \"sourceMaps\": true,\n      \"outFiles\": [\"${workspaceFolder}/out/**/*.js\"],\n      \"env\": {\n        \"NODE_ENV\": \"development\"\n      }\n    }\n  ]\n}\n```\n\n**Other debugging techniques:**\n```typescript\n// Enable DevTools only in development\nif (process.env.NODE_ENV === 'development') {\n  win.webContents.openDevTools({ mode: 'detach' });\n}\n\n// Inspect specific renderer processes from command line:\n// electron . --inspect=5858 --remote-debugging-port=9223\n```\n\n#### Testing Strategy\n\n**Unit testing (Vitest / Jest):**\n```typescript\n// tests/unit/store.test.ts\nimport { describe, it, expect, vi } from 'vitest';\n\n// Mock Electron modules for unit tests\nvi.mock('electron', () => ({\n  app: { getPath: () => '/tmp/test' },\n}));\n\ndescribe('Store', () => {\n  it('returns default values for missing keys', () => {\n    // Test store logic without Electron runtime\n  });\n});\n```\n\n**E2E testing (Playwright + Electron):**\n```typescript\n// tests/e2e/app.spec.ts\nimport { test, expect, _electron as electron } from '@playwright/test';\n\ntest('app launches and shows main window', async () => {\n  const app = await electron.launch({ args: ['.'] });\n  const window = await app.firstWindow();\n\n  // Wait for the app to fully load\n  await window.waitForLoadState('domcontentloaded');\n\n  const title = await window.title();\n  expect(title).toBe('My App');\n\n  // Take a screenshot for visual regression\n  await window.screenshot({ path: 'tests/screenshots/main-window.png' });\n\n  await app.close();\n});\n\ntest('file open dialog works via IPC', async () => {\n  const app = await electron.launch({ args: ['.'] });\n  const window = await app.firstWindow();\n\n  // Test IPC by evaluating in the renderer context\n  const version = await window.evaluate(async () => {\n    return window.electronAPI.invoke('app:get-version');\n  });\n\n  expect(version).toBeTruthy();\n  await app.close();\n});\n```\n\n**Playwright config for Electron:**\n```typescript\n// playwright.config.ts\nimport { defineConfig } from '@playwright/test';\n\nexport default defineConfig({\n  testDir: './tests/e2e',\n  timeout: 30_000,\n  retries: 1,\n  use: {\n    trace: 'on-first-retry',\n    screenshot: 'only-on-failure',\n  },\n});\n```\n\n---\n\n## Application Lifecycle Management\n\n```typescript\n// src/main/main.ts\nimport { app, BrowserWindow } from 'electron';\nimport { registerIpcHandlers } from './ipc-handlers';\nimport { setupAutoUpdater } from './updater';\nimport { store } from './store';\n\nlet mainWindow: BrowserWindow | null = null;\n\napp.whenReady().then(() => {\n  registerIpcHandlers();\n  mainWindow = createMainWindow();\n\n  // Restore window bounds\n  const bounds = store.get('windowBounds');\n  if (bounds) mainWindow.setBounds(bounds);\n\n  // Save window bounds on close\n  mainWindow.on('close', () => {\n    if (mainWindow) store.set('windowBounds', mainWindow.getBounds());\n  });\n\n  // Auto-update (only in production)\n  if (app.isPackaged) {\n    setupAutoUpdater(mainWindow);\n  }\n\n  // macOS: re-create window when dock icon is clicked\n  app.on('activate', () => {\n    if (BrowserWindow.getAllWindows().length === 0) {\n      mainWindow = createMainWindow();\n    }\n  });\n});\n\n// Quit when all windows are closed (except on macOS)\napp.on('window-all-closed', () => {\n  if (process.platform !== 'darwin') {\n    app.quit();\n  }\n});\n\n// Security: prevent additional renderers from being created\napp.on('web-contents-created', (_event, contents) => {\n  contents.on('will-attach-webview', (event) => {\n    event.preventDefault(); // Block <webview> tags\n  });\n});\n```\n\n---\n\n## Common Issue Diagnostics\n\n### White Screen on Launch\n**Symptoms**: App starts but renderer shows a blank/white page\n**Root causes**: Incorrect `loadFile`/`loadURL` path, build output missing, CSP blocking scripts\n**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.\n\n### IPC Messages Not Received\n**Symptoms**: `invoke()` hangs or `send()` has no effect\n**Root causes**: Channel name mismatch, preload not loaded, contextBridge not exposing the channel\n**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.\n\n### Native Module Crashes\n**Symptoms**: App crashes on startup with `MODULE_NOT_FOUND` or `invalid ELF header`\n**Root causes**: Native module compiled for wrong Electron/Node ABI version\n**Solutions**: Run `npx @electron/rebuild` after installing native modules. Ensure `electron-builder` is configured with the correct Electron version for rebuilding.\n\n### App Not Updating\n**Symptoms**: `autoUpdater.checkForUpdates()` returns nothing or errors\n**Root causes**: Missing `publish` config, unsigned app (macOS), incorrect GitHub release assets\n**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).\n\n### Large Bundle Size (>200MB)\n**Symptoms**: Built application is excessively large\n**Root causes**: Dev dependencies bundled, no tree-shaking, duplicate Electron binaries\n**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`.\n\n---\n\n## Best Practices\n\n- ✅ **Always** set `contextIsolation: true` and `nodeIntegration: false`\n- ✅ **Always** use `contextBridge` in preload with an explicit channel whitelist\n- ✅ **Always** validate IPC inputs in the main process — treat renderer as untrusted\n- ✅ **Always** use `ipcMain.handle()` / `ipcRenderer.invoke()` for request/response IPC\n- ✅ **Always** configure Content Security Policy headers\n- ✅ **Always** sanitize URLs before passing to `shell.openExternal()`\n- ✅ **Always** code-sign your production builds\n- ✅ Use Playwright with `@playwright/test`'s Electron support for E2E tests\n- ✅ Store user data in `app.getPath('userData')`, never in the app directory\n- ❌ **Never** set `nodeIntegration: true` — this is the #1 Electron security vulnerability\n- ❌ **Never** expose raw `ipcRenderer` or `require()` to the renderer context\n- ❌ **Never** use `remote` module (deprecated and insecure)\n- ❌ **Never** use `ipcRenderer.sendSync()` — it blocks the renderer event loop\n- ❌ **Never** disable `webSecurity` in production\n- ❌ **Never** load remote/untrusted content without a strict CSP and sandboxing\n\n## Limitations\n\n- Electron bundles Chromium + Node.js, resulting in a minimum ~150MB app size — this is a fundamental trade-off of the framework\n- Not suitable for apps where minimal install size is critical (consider Tauri instead)\n- Single-window apps are simpler to architect; multi-window state synchronization requires careful IPC design\n- Auto-update on Linux requires distributing via Snap, Flatpak, or custom mechanisms — `electron-updater` has limited Linux support\n- macOS notarization requires an Apple Developer account ($99/year) and is mandatory for distribution outside the Mac App Store\n- Debugging main process issues requires VS Code or Chrome DevTools via `--inspect` flag — there is no integrated debugger in Electron itself\n\n## Related Skills\n\n- `chrome-extension-developer` — When building browser extensions instead of desktop apps (shares multi-process model concepts)\n- `docker-expert` — When containerizing Electron's build pipeline or CI/CD\n- `react-patterns` / `react-best-practices` — When using React for the renderer UI\n- `typescript-pro` — When setting up advanced TypeScript configurations for multi-target builds\n- `nodejs-backend-patterns` — When the main process needs complex backend logic\n- `github-actions-templates` — When setting up CI/CD for cross-platform Electron builds","tags":["electron","development","antigravity","awesome","skills","sickn33","agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows"],"capabilities":["skill","source-sickn33","skill-electron-development","topic-agent-skills","topic-agentic-skills","topic-ai-agent-skills","topic-ai-agents","topic-ai-coding","topic-ai-workflows","topic-antigravity","topic-antigravity-skills","topic-claude-code","topic-claude-code-skills","topic-codex-cli","topic-codex-skills"],"categories":["antigravity-awesome-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/sickn33/antigravity-awesome-skills/electron-development","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add sickn33/antigravity-awesome-skills","source_repo":"https://github.com/sickn33/antigravity-awesome-skills","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 34831 github stars · SKILL.md body (27,651 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-04-24T06:51:06.777Z","embedding":null,"createdAt":"2026-04-18T21:36:28.071Z","updatedAt":"2026-04-24T06:51:06.777Z","lastSeenAt":"2026-04-24T06:51:06.777Z","tsv":"'-8':835,874,1465 '/ipc-handlers':2288 '/node_modules/.bin/electron':2052 '/out':2063 '/preload/preload.js':524 '/renderer':1333 '/store':2296 '/tests/e2e':2258 '/tmp/test':2125 '/updater':2292 '0':825,833,839,1433,1435,2355 '0.2.0':2038 '000':2261 '1':218,268,1380,2263,2740 '1000':1860 '1024x1024':335 '1200':490,1437,1538 '150mb':2794 '2':227,404,1496,1503 '200mb':2612 '3':237,602 '30':2260 '4':248,1023,1853,1857 '403':1364 '5':256,1374 '5173':1196 '5858':2094 '6':1608 '60':1858,1859 '7':1955 '800':492,1439,1543 '9223':2058,2099 '99/year':2864 'abi':2536 'abus':1241 'access':423,425 'account':2863 'across':377,1377 'action':1268,2969 'activ':2351 'addit':525,2378 'advanc':2947 'allow':645,663,685,692,741,1188 'allowed_receive_channels.includes':756 'allowed_send_channels.includes':709,727 'allowedhost':1220 'allowedhosts.has':1248 'allowrunninginsecurecont':534,1038 'allowtochangeinstallationdirectori':1679 'altern':181 'alway':2656,2663,2673,2685,2692,2698,2705 'analyz':219 'api':431,1143 'app':7,86,105,147,176,207,278,333,392,427,652,670,879,1148,1192,1310,1321,1391,1395,1622,1757,1924,2123,2156,2164,2175,2190,2212,2235,2281,2407,2441,2516,2559,2574,2588,2731,2795,2810,2823,2873,2909 'app.close':2202,2243 'app.firstwindow':2171,2219 'app.getpath':1451,2726 'app.ispackaged':2337 'app.on':2350,2367,2383 'app.quit':2375 'app.tsx':314 'app.whenready':1318,2302 'appid':1618 'appimag':1687 'appl':1712,2861 'applic':44,63,80,133,162,294,2275,2615 'appropri':254 'appstat':1410,1427,1444,1458,1476,1485 'arch':1658,1663,1671 'architect':2827 'architectur':18,45,212,271,344 'archiv':1132,1887 'area':267 'arg':706,713,724,732,752,762,765,911,917,924,2053,2167,2215 'argument':868 'arm64':1660,1665,1673 'array':2511 'asar':1131,1638,1879 'asset':1306,1636,2579 'async':807,844,933,1322,2162,2210,2232 'attach':2393 'attack':598 'audit':1918,2632 'auto':27,69,116,301,1005,1158,1694,1767,2331,2838 'auto-upd':26,68,115,300,1004,1157,1766,2330,2837 'autoupdat':1776 'autoupdater.autodownload':1799 'autoupdater.autoinstallonappquit':1804 'autoupdater.checkforupdates':1856,1861,2563 'autoupdater.downloadupdate':1870 'autoupdater.logger':1797 'autoupdater.on':1806,1818,1831,1838 'autoupdater.quitandinstall':1874 'avail':187,673,900,958,962,1809,1813 'await':811,830,869,938,1367,2165,2170,2179,2184,2197,2201,2213,2218,2230,2242 'backend':203,2957,2965 'backend/server':198 'background':463 'base':179 'basedir':1330,1341,1354,1358 'bash':1709 'belong':396 'best':172,2654,2932 'binari':2630 'blank/white':2413 'blind':856 'block':1013,1111,1201,1256,1270,2397,2425,2765 'bound':2309,2311,2315,2317,2320 'boundari':226,379 'bridg':306,447 'broadcast':1560 'broadcasttoallwindow':1571,1602 'browser':2904 'browserview':1096 'browserwindow':467,474,484,488,783,1175,1567,1782,1795,2282,2299 'browserwindow.getallwindows':1581,2353 'build':76,158,174,188,205,252,357,1609,1719,1755,1987,1991,2421,2711,2903,2923,2954,2980 'builder':21,109,1614,1761,1894,1928,2549 'buildresourc':1626 'built':1634,2614 'bundl':150,1145,1875,1947,2610,2623,2787 'bundler':1908,2639 'busi':394 'bytespersecond':1829 'callback':543,544,751,764,923 'cancel':809,823 'care':2834 'case':974 'casual':1137 'catch':1260,1470 'categori':1644,1690 'caus':2416,2473,2529,2569,2620 'certif':1714,1738 'chang':497,1562,1589,1606 'channel':240,291,323,611,643,647,665,687,694,704,710,712,722,728,731,737,738,749,757,767,771,909,915,921,1046,1572,1585,2474,2484,2487,2506,2671 'check':1849,2442,2503,2644 'checklist':262,1028 'chrome':189,193,2883,2899 'chrome-extension-develop':192,2898 'chromium':2788 'ci/cd':2926,2974 'class':1440 'cleanup':964 'click':2349 'close':2322,2324,2363,2371 'code':23,65,375,595,895,1149,1707,1736,2592,2707,2881 'code-sign':2591,2706 'com.mycompany.myapp':1619 'command':2090 'common':2399 'communic':94,605,613 'compil':1630,1904,2532 'complex':2964 'compon':318 'compress':1640,1889,2652 'concept':2915 'concurr':1969,2024 'config':1895,2245,2572 'configur':358,1151,1615,2039,2551,2693,2949 'confirm':2496 'consid':1933,2817 'consol':2444 'console.log':944,960,1548 'console.warn':1200,1255,1261 'const':485,644,661,662,681,758,808,828,936,954,1182,1214,1219,1324,1329,1345,1351,1365,1425,1460,1498,1517,1578,2163,2168,2182,2211,2216,2228,2310 'constant':370 'constants.ts':321 'constructor':1448 'contain':367,2600 'container':2920 'content':538,548,829,840,848,862,872,946,1068,1069,1090,2386,2389,2694,2778 'content-security-polici':547 'contents.on':2390 'context':505,1063,2227,2753 'contextbridg':454,629,635,902,1043,2480,2665 'contextbridge.exposeinmainworld':696 'contextisol':12,87,231,499,584,1030,2658 'control':1230 'core':265 'correct':2500,2554 'cpu':460 'cpu-intens':459 'crash':128,2514,2517 'creat':1173,2343,2382,2387 'createmainwindow':483,2306,2357 'credenti':1141 'critic':578,985,2816 'cross':2977 'cross-platform':2976 'csc':1721,1725,1744,1749 'csp':89,1072,2424,2446,2782 'custom':1275,1301,2848 'cwd':2048 'dark':1413,1527,1547,1551,1598 'darwin':2374 'data':570,575,855,993,1088,1366,1373,1443,1574,1586,2724 'deb':1689 'debug':122,1958,2031,2041,2056,2070,2097,2875 'debugg':2892 'decid':1803 'declar':903 'decodeuricompon':1348 'dedic':401 'deep':48,197 'default':230,443,470,495,552,1426,1467,1472,1528,1537,1542,2130,2255 'default-src':551 'defineconfig':2251,2256 'deni':1269 'depend':1898,1954,2622,2650 'deprec':2758 'describ':2109,2126 'design':238,419,2836 'desktop':6,43,79,164,180,2908 'detach':2084 'detail':542 'details.responseheaders':546 'dev':1197,1897,1968,1972,1976,1978,1981,1986,2027,2453,2621 'devdepend':2646 'develop':3,8,30,185,195,1691,1713,1956,1959,2023,2068,2077,2081,2449,2862,2901 'devtool':2074,2443,2884 'diagnost':2401 'dialog':144,656,782,992,2206 'dialog.showopendialog':812 'directori':366,1623,2732 'dirnam':523,1332 'disabl':2771 'dist':1625 'distribut':103,165,1611,2843,2869 'dmg':1657 'dock':2346 'docker':2917 'docker-expert':2916 'docs.example.com':1224 'dom':424 'domcontentload':2181 'download':677,1002,1820,1834,1837,1869 'download-progress':1819 'duplic':2628 'e2e':341,2141,2720 'effect':2471 'electron':2,5,20,29,35,51,78,85,104,108,112,120,146,277,411,476,638,785,1251,1273,1283,1397,1505,1515,1569,1613,1760,1771,1779,1784,1789,1893,1927,1984,1989,1993,1997,2001,2029,2092,2116,2122,2139,2144,2150,2152,2247,2284,2458,2548,2555,2629,2717,2741,2786,2851,2894,2921,2979 'electron-build':19,107,1612,1759,1892,1926,2547 'electron-builder.yml':280,1617,2585,2636 'electron-develop':1 'electron-forg':111,2000 'electron-log':1788 'electron-stor':1504,1514 'electron-updat':119,1770,1778,2850 'electron-vit':1983,1988,1996 'electron.ipcrendererevent':761 'electron.launch':2166,2214 'electron/node':2535 'electron/rebuild':1934,2541 'electronapi':697,897,907 'elf':2526 'els':1254 'enabl':1133,1238,2073 'enforc':228,529 'engin':36 'ensur':2450,2546,2596 'entir':1949 'entitl':338,1651 'entitlements.mac.plist':336 'entitlementsinherit':1653 'entri':288,312,347 'enum':326,372,1525 'env':2065,2067,2080 'environ':1716,1740 'equival':2606 'err':1840,1844 'err.message':1848 'error':679,736,866,1839,1843,1847,2567 'esbuild':1911 'ev':1733 'eval':1081 'evalu':2223 'event':760,845,1017,1109,1118,1180,1595,2388,2395,2768 'event.preventdefault':1199,2396 'everi':1852 'exact':2490 'example.com':1223 'except':2364 'excess':2617 'exclud':1896,1931 'execut':374,596 'exist':2436 'expect':2111,2149,2186,2239 'experi':1957 'experimentalfeatur':536 'expert':216,2918 'expertis':49,266 'explicit':242,630,1045,2670 'exploit':1124 'export':792,1497,1720,1724,1742,1747,1791,2254 'expos':590,641,1060,1862,2482,2745 'extens':190,194,819,2900,2905 'extern':1257,1264 'externalurl':1215 'externalurl.hostname':1249 'externalurl.protocol':1246 'externalurl.tostring':1253 'failur':2274 'fals':234,507,535,537,585,1033,1039,1650,1678,1682,1800,2662 'fast':2007 'featur':138 'file':142,648,650,666,668,805,818,842,930,940,945,990,1628,1899,1930,2204,2608,2633 'filepath':810,832,838,846,859,871,1352,1369,1446 'filepath.startswith':1357 'filepaths.length':824 'filter':815 'fire':976 'fire-and-forget':975 'first':2268 'flag':2887 'flatpak':2846 'flow':622 'flutter':215 'flutter-expert':214 'font':572 'font-src':571 'forbidden':1362 'forg':113,2002 'forge.config.ts':282 'forget':978 'forward':1226 'found':2523 'framework':2806 'freez':1020 'fs':1403 'fs/promises':791,1293 'full':434,444,465 'fulli':2177 'function':482,793,934,1083,1570,1792 'fundament':2800 'gatekeeperassess':1649 'get':654,881,1473,2237 'get-vers':653,880,2236 'getpath':2124 'github':1698,2577,2598,2968 'github-actions-templ':2967 'global':899,904 'grade':42 'handl':388 'handler':292,777,1244 'hang':2466 'harden':526,1025 'hardenedruntim':1647 'header':1073,2527,2697 'height':491,1423,1438,1539 'hijack':1167 'hmr':2008 'hot':1962 'hour':1854 'html':313 'https':1161,1247 'hub':433 'icon':334,2347 'icon.png':332 'identifi':224 'img':567 'img-src':566 'implement':114,196,249 'import':376,473,477,634,780,786,1280,1284,1289,1294,1394,1398,1404,1511,1566,1775,1781,1785,2108,2147,2250,2280,2285,2289,2293 'includ':1903 'incorrect':2417,2576 'index.html':311 'info':1810 'info.releasenotes':1817 'info.version':1815 'inlin':565 'innerhtml':1085 'input':851,1050,2676 'insecur':2760 'inspect':1138,2085,2093,2886 'instal':1873,2543,2813 'instead':1938,2819,2906 'instruct':217 'integr':62,135,2891 'intens':461 'interact':440 'intercept':1110,1119 'interfac':330,905,1409 'invalid':867,1263,2525 'invok':721,796,914,2465 'ipc':11,57,93,239,290,322,389,432,604,606,620,776,932,968,1040,1049,1866,2209,2221,2460,2675,2691,2835 'ipc-handlers.ts':289 'ipcmain':781 'ipcmain.handle':804,841,878,989,1590,1867,1871,2687 'ipcmain.on':980 'ipcrender':636,1059,2747 'ipcrenderer.invoke':730,988,2688 'ipcrenderer.on':766,999 'ipcrenderer.removelistener':770 'ipcrenderer.send':711,979 'ipcrenderer.sendsync':1009,1067,2763 'isol':417,501 'issu':125,2400,2878 'jest':2105 'js':2064 'json':1964,2035 'json.parse':1468 'json.stringify':1493 'k':1475,1477,1483,1486 'keep':380,1340 'key':343,1144,1474,1480,1482,1489,1726,1750,2134 'larg':2609,2618 'latest-mac.yml':2604 'launch':2047,2157,2405 'layout':274 'lead':1336 'lean':383 'length':2354 'let':1801,2297 'level':516 'lifecycl':134,393,428,2276 'light':1412,1429,1526,1529,1597 'lightweight':1507 'limit':452,2785,2854 'line':2091 'link':1236,1722,1745 'linux':1683,1684,2841,2855 'listen':759,768,772 'load':457,1093,1304,1457,2178,2479,2776 'loadfil':2418 'loadurl':2419 'local':1305 'localhost':1195 'log':981,1786,1790,1798 'log.error':1841 'logic':199,303,395,2137,2966 'loop':1018,2769 'mac':1643,1762,2872 'mac.zip':2602 'maco':337,1156,1642,1710,2340,2366,2575,2587,2857 'main':96,123,284,286,308,349,381,384,407,426,449,615,702,718,745,774,952,1055,1170,1381,1558,1631,1977,1982,2020,2033,2042,2160,2493,2679,2876,2961 'main.ts':285 'maintain':1692 'mainwindow':1794,2298,2305,2326,2339,2356 'mainwindow.getbounds':2329 'mainwindow.on':2323 'mainwindow.setbounds':2316 'mainwindow.webcontents.send':1811,1823,1835,1845 'manag':129,391,1376,2277 'mandatori':471,1029,2867 'master':4 'match':2489 'math.round':1827 'maximum':1641,1890,2653 'md':821 'mechan':2849 'menu':295 'menu.ts':293 'menus':139 'messag':2461 'method':972 'minim':1102,2812 'minimum':2793 'mismatch':2476 'miss':2133,2423,2570 'mobil':206 'mock':2115 'mode':2083 'model':56,406,2914 'modern':2006 'modul':363,1920,1937,1951,2117,2513,2521,2531,2545,2757 'multi':16,54,1553,2829,2912,2952 'multi-process':15,53,2911 'multi-target':2951 'multi-window':1552,2828 'multipl':130,413 'must':621,2589 'my-electron-app':275 'name':324,816,2040,2475,2488 'nativ':60,136,211,430,1936,2512,2530,2544 'navig':1104,1108,1166,1179,1189,1202 'need':2963 'never':373,496,579,852,1007,1225,1765,2728,2733,2744,2754,2761,2770,2775 'new':77,487,735,865,1082,1116,1184,1206,1216,1221,1272,1326,1360,1371,1500,1519 'new-window':1115 'nextj':171 'nextjs-best-practic':170 'node':480,790,1287,1292,1297,1402,1407,1919,1950,2015,2045,2066 'node.js':422,2789 'nodeintegr':90,233,506,581,1032,2661,2735 'nodej':202,2956 'nodejs-backend-pattern':201,2955 'non':984 'non-crit':983 'none':435,441,466 'notar':2595,2858 'noth':2565 'notif':141,986 'npm':1970,1974 'npx':1758,2540 'nsis':1670,1676 'null':827,1495,2300,2301 'number':688,695,1418,1420,1422,1424,1536,1541 'object':1532 'on-first-retri':2266 'one':699,743 'one-way':698,742 'oneclick':1677 'only-on-failur':2271 'open':651,659,669,806,928,941,1210,2205 'openfil':814,935 'oper':991 'optim':145,1877 'orchestr':386 'org':1702 'origin':532 'os':61,137,515,1234 'os-level':514 'outfil':2061 'output':1624,1905,2422 'outsid':2870 'owner':1699 'packag':22,64,101,1130,1882,2440 'package.json':279,1637,1965 'page':456,2414 'parsedurl':1183 'parsedurl.origin':1194 'pass':2431,2702 'password':1727,1730,1751,1754 'path':478,481,837,1285,1288,1405,1408,2199,2420,2430,2498 'path.join':522,1450 'path.normalize':1347 'path.resolve':1331,1339,1353 'path/to/code-signing.pfx':1746 'path/to/developer_id_application.p12':1723 'pattern':59,169,204,797,969,971,1900,1932,2634,2929,2958 'percent':1826 'perform':148 'permachin':1681 'permiss':1099,1103 'persist':1508 'phish':1239 'pipelin':2924 'platform':1242,1944,2978 'playwright':2143,2244,2713 'playwright.config.ts':2249 'playwright/test':2154,2253,2715 'plugin':2005 'point':348 'polici':533,540,550,1071,2696 'pop':1122 'pop-up':1121 'port':2057,2098 'practic':173,2655,2933 'prebuilt':1941 'preload':13,99,246,304,350,409,445,502,519,521,625,627,1041,1632,2477,2492,2497,2667 'preload.ts':305 'prevent':508,1120,1165,1205,2377 'principl':345 'privat':1442,1445,1456 'privileg':1311 'pro':2943 'process':17,55,100,124,225,287,378,382,403,405,414,420,517,618,775,953,1056,1171,1378,1382,1559,2021,2034,2043,2088,2680,2877,2913,2962 'process.env.node':2079 'process.platform':2373 'process.versions.electron':884 'product':41,260,587,1011,1026,1953,2335,2710,2774 'production-grad':40 'productnam':1620 'progress':675,1000,1821,1822,1825 'progress.bytespersecond':1830 'progress.percent':1828 'project':221,269,273 'promis':919 'promise.reject':734 'properti':813,1533 'protect':1134 'protocol':1276,1281,1302 'protocol.handle':1320 'protocol.registerschemesasprivileged':1100,1308 'provid':1697 'public.app-category.developer':1645 'publish':1696,1764,2571,2582 'push':995 'queri':994 'quit':2358 'raw':1058,1227,1461,1469,2746 'rce':597 're':2342 're-creat':2341 'react':168,210,2928,2931,2936 'react-best-practic':2930 'react-native-architectur':209 'react-pattern':167,2927 'react/vue/svelte/vanilla':317 'readfil':787,831,1290,1368 'readfilesync':1399,1462 'rebuild':2558 'receiv':664,693,2463 'receivechannel':690,750 'recentfil':1414,1430 'recommend':272,1388,1994 'regist':1299 'registeripchandl':794,2286,2304 'registr':1277 'regress':2196 'reject':1262 'relat':2437,2896 'relativepath':1346,1355 'releas':2578,2599 'releasenot':1816 'reload':1963 'remot':594,1089,2055,2096,2756 'remote-debugging-port':2054,2095 'remote/untrusted':2777 'render':97,127,309,310,352,399,408,436,438,451,504,511,592,617,701,717,719,746,803,854,885,894,997,1015,1062,1229,1633,1635,1864,1917,1973,1979,2010,2026,2087,2226,2379,2410,2495,2643,2682,2752,2767,2939 'renderer-control':1228 'replac':1350 'repo':1703,1706 'request':1323,2046 'request.url':1328 'request/response':720,987,2690 'requir':509,1250,1711,1732,2749,2833,2842,2859,2879 'resourc':331,1627 'resources/entitlements.mac.plist':1652,1654 'respons':1361,1372 'responsehead':545 'restor':2307 'restrict':1098 'result':937,943,2790 'result.content':947 'retri':2262,2269 'return':576,729,733,769,773,798,826,836,875,883,1267,1359,1370,1466,1471,1478,2129,2233,2564 'role':421 'root':316,1344,2415,2472,2528,2568,2619 'run':412,1971,1975,2017,2025,2456,2539 'runtim':2140 'runtimeexecut':2050 'rust':178 'rust-bas':177 'safe':610,889 'same-origin':530 'sandbox':88,235,512,518,1034,2784 'sanit':2699 'save':649,667,843,2318 'schema':1521 'scheme':1309 'scratch':82 'screen':2403 'screenshot':2193,2270 'script':14,247,520,556,626,628,1966,1967,2426 'script-src':555 'section':2583 'secur':10,39,58,83,229,261,446,469,494,539,549,603,1024,1027,1070,1278,1307,1314,2376,2695,2742 'self':554,558,562,569,574 'send':646,686,703,908,2468 'sendchannel':683,705,723 'senior':34 'sensit':1140 'separ':346,1095 'server':1198,2028,2454 'set':91,580,589,1074,1222,1481,1591,1593,1603,1715,1739,1888,2657,2734,2945,2972 'set-them':1592 'setinterv':1855 'settings.json':1453 'setup':1960 'setupautoupdat':1793,2290,2338 'sha256':1675 'shake':1915,2627 'share':320,325,328,359,362,365,2910 'shell.openexternal':1126,1252,2704 'ship':264,1921,1940 'show':658,2159,2411 'show-open':657 'sign':24,66,1150,1610,1708,1737,1756,2593,2708 'signatur':1164 'signinghashalgorithm':1674 'simpler':2825 'simultan':2030 'singl':1384,1886,2821 'single-window':2820 'size':151,1876,2611,2796,2814 'skill':74,156,2897 'skill-electron-development' 'slash':1337 'snap':2845 'solut':2427,2485,2538,2580,2631 'sourc':1135,1385,1883 'source-sickn33' 'sourcemap':2059 'special':37 'specif':642,2086 'src':283,553,557,561,568,573 'src/main/ipc-handlers.ts':779 'src/main/main.ts':2279 'src/main/store.ts':1393 'src/main/updater.ts':1774 'src/preload/preload.ts':633 'src/renderer/app.tsx':891 'standard':1312,1735 'start':1992,2408,2459 'startup':2519 'state':1375,1555,1561,2831 'status':1003,1363 'storag':1509 'store':1441,1499,1501,1506,1512,1516,1518,1520,2127,2136,2294,2722,2874 'store.get':1549,2312 'store.set':1545,1599,2327 'strategi':71,1379,1502,2101 'strict':2781 'string':847,849,860,863,910,916,922,1415,1447,1524,1573 'strip':1334 'structur':222,270 'style':319,560 'style-src':559 'subscrib':948 'subscript':747 'success':876 'suitabl':2808 'summari':970 'support':2718,2856 'supportfetchapi':1316 'symptom':2406,2464,2515,2562,2613 'synchron':1556,2832 'system':143,297 'tag':2398 'take':2191 'target':1655,1656,1661,1668,1669,1685,1686,1688,2953 'task':462 'tauri':175,184,2818 'tauri-develop':183 'techniqu':2071 'telemetri':982 'templat':2970 'test':250,339,2100,2103,2120,2135,2142,2148,2155,2203,2220,2721 'testdir':2257 'tests/e2e/app.spec.ts':2146 'tests/screenshots/main-window.png':2200 'tests/unit/store.test.ts':2107 'text':817 'theme':1411,1428,1522,1546,1550,1588,1594,1596,1600,1601,1605,1607 'theme-chang':1604 'this.data':1454,1479,1488,1494 'this.filepath':1449,1463,1492 'this.load':1455 'throw':864 'timeout':2259 'titl':2183,2187 'tobe':2188 'tobetruthi':2241 'tool':255,1646 'toolchain':1995 'topic-agent-skills' 'topic-agentic-skills' 'topic-ai-agent-skills' 'topic-ai-agents' 'topic-ai-coding' 'topic-ai-workflows' 'topic-antigravity' 'topic-antigravity-skills' 'topic-claude-code' 'topic-claude-code-skills' 'topic-codex-cli' 'topic-codex-skills' 'trace':2265 'trade':2802 'trade-off':2801 'tray':140,298 'tray.ts':296 'treat':2681 'tree':1914,2626 'tree-shak':1913,2625 'tri':1213,1459 'true':232,236,500,513,528,582,877,1031,1035,1037,1313,1315,1317,1639,1648,1680,1805,1880,2060,2659,2736 'trust':853 'truth':1387 'ts':2014 'ts-node':2013 'tsconfig.json':342 'tsx':2011 'two':715 'two-way':714 'txt':820 'type':360,369,682,689,888,1523,1531,1535,1540,2044 'type-saf':887 'typeof':684,691,858,861 'types.ts':327 'typescript':329,472,632,778,890,1168,1279,1392,1510,1557,1773,2018,2072,2106,2145,2248,2278,2942,2948 'typescript-pro':2941 'ui':315,437,1022,2940 'unexpect':1112 'unit':340,2102,2119 'unknown':707,725,753,763,912,918,925,1575 'unmount':966 'unsaf':564 'unsafe-inlin':563 'unsanit':1128 'unsign':2573 'unsubscrib':955,967 'untrust':1087,2684 'unvalid':1235 'updat':28,70,117,121,302,672,674,676,678,950,957,961,1001,1006,1159,1695,1768,1772,1780,1808,1812,1824,1833,1836,1842,1846,1851,1868,1872,2332,2561,2839,2852 'update-avail':1807 'update-download':1832 'updater.ts':299 'url':1113,1129,1181,1185,1186,1204,1212,1217,1218,1231,1243,1258,1259,1265,1266,1295,1298,1325,1327,2700 'url.pathname':1349 'usag':886,1544 'use':72,154,166,182,191,200,208,973,1008,1042,1065,1079,1101,1160,1878,1906,1925,2264,2637,2651,2664,2686,2712,2755,2762,2935 'user':439,1802,2723 'userdata':1452,2727 'utf':834,873,1464 'util':410,458 'valid':257,850,1052,2674 'valu':800,1484,1490,2131 'variabl':1717,1741 'verifi':1163,2428,2486,2581 'version':655,671,882,959,963,1814,2037,2229,2238,2240,2537,2556 'vi':2112 'vi.mock':2121 'via':453,901,931,1865,2208,2844,2885 'violat':2447 'visual':2195 'vite':1909,1980,1985,1990,1998,2004 'vite/esbuild':2640 'vite/webpack':2452 'vitest':2104,2114 'void':754,795,913,926,927,1487,1576,1796 'vs':2880 'vscode/launch.json':2036 'vulner':601,2743 'wait':2172 'way':700,716,744 'web':160,2385 'web-contents-cr':2384 'web-on':159 'webcont':1105,1114 'webcontents.send':998 'webpack':1910 'webprefer':493,2502 'websecur':527,1036,2772 'webview':2394 'white':2402 'whitelist':243,631,639,1047,2510,2672 'width':489,1421,1436,1534 'will-attach-webview':2391 'will-navig':1106,1177 'win':486,577,1579,1667,1743,1748,1763 'win.isdestroyed':1583 'win.loadfile':2433 'win.loadurl':2435 'win.webcontents.on':1176 'win.webcontents.opendevtools':2082 'win.webcontents.send':1584 'win.webcontents.session.webrequest.onheadersreceived':541 'win.webcontents.setwindowopenhandler':1211 'window':131,387,429,906,1077,1117,1154,1207,1274,1554,1565,1666,1731,2161,2169,2217,2308,2319,2344,2361,2369,2607,2822,2830 'window-all-clos':2368 'window.electronapi.invoke':939,2234 'window.electronapi.on':956 'window.evaluate':2231 'window.screenshot':2198 'window.title':2185 'window.waitforloadstate':2180 'windowbound':1416,1431,1530,2313,2328 'within':1190 'without':163,2138,2779 'work':464,2207 'worker':402 'workspacefold':2049,2051,2062 'writefil':788,870 'writefilesync':1400,1491 'wrong':2534 'x':1417,1432 'x64':1659,1664,1672 'xss':600 'y':1419,1434 'yaml':1616 'your-email@example.com':1693 'your-org':1700 'your-password':1728,1752 'your-repo':1704 'zip':1662","prices":[{"id":"0e8d7c57-5f8c-4bfb-9256-b9a6af8dd155","listingId":"b501a5da-2d11-458d-be6d-f1cd4a33a674","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"sickn33","category":"antigravity-awesome-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T21:36:28.071Z"}],"sources":[{"listingId":"b501a5da-2d11-458d-be6d-f1cd4a33a674","source":"github","sourceId":"sickn33/antigravity-awesome-skills/electron-development","sourceUrl":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/electron-development","isPrimary":false,"firstSeenAt":"2026-04-18T21:36:28.071Z","lastSeenAt":"2026-04-24T06:51:06.777Z"}],"details":{"listingId":"b501a5da-2d11-458d-be6d-f1cd4a33a674","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"sickn33","slug":"electron-development","github":{"repo":"sickn33/antigravity-awesome-skills","stars":34831,"topics":["agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows","antigravity","antigravity-skills","claude-code","claude-code-skills","codex-cli","codex-skills","cursor","cursor-skills","developer-tools","gemini-cli","gemini-skills","kiro","mcp","skill-library"],"license":"mit","html_url":"https://github.com/sickn33/antigravity-awesome-skills","pushed_at":"2026-04-24T06:41:17Z","description":"Installable GitHub library of 1,400+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. Includes installer CLI, bundles, workflows, and official/community skill collections.","skill_md_sha":"c48430bb593c32f7d0bf485a290566e877d0c0e1","skill_md_path":"skills/electron-development/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/electron-development"},"layout":"multi","source":"github","category":"antigravity-awesome-skills","frontmatter":{"name":"electron-development","description":"Master Electron desktop app development with secure IPC, contextIsolation, preload scripts, multi-process architecture, electron-builder packaging, code signing, and auto-update."},"skills_sh_url":"https://skills.sh/sickn33/antigravity-awesome-skills/electron-development"},"updatedAt":"2026-04-24T06:51:06.777Z"}}