Skip to main content
ClaudeWave
Install in Claude Code
Copy
git clone --depth 1 https://github.com/ww-w-ai/bkit-claude-code /tmp/desktop-app && cp -r /tmp/desktop-app/skills/desktop-app ~/.claude/skills/desktop-app
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# Desktop App Development Expertise

## Overview

A guide for developing desktop apps using web technologies (HTML, CSS, JavaScript).
Support Windows, macOS, and Linux simultaneously with a single codebase.

---

## Framework Selection Guide

### Framework Selection by Tier (v1.3.0)

| Framework | Tier | Recommendation | Use Case |
|-----------|------|----------------|----------|
| **Tauri** | Tier 2 | ⭐ Primary | Lightweight (3MB), Rust security |
| **Electron** | Tier 3 | Supported | Mature ecosystem, VS Code-like apps |

> **AI-Native Recommendation**: Tauri
> - 35% YoY growth
> - 20-40MB memory vs Electron's 200-400MB
> - Mobile support (iOS/Android) via Tauri 2.0
> - Rust backend = memory safety

> **Ecosystem Recommendation**: Electron
> - Mature tooling
> - Node.js integration
> - Proven at scale (VS Code, Slack)

### Level-wise Recommendations

```
Starter → Tauri (v2) [Tier 2]
  - Simpler setup than Electron
  - Smaller output bundles (~3MB vs ~150MB)

Dynamic → Tauri + auto-update [Tier 2]
  - Includes server integration, auto-update
  - Lower memory footprint

Enterprise → Tauri [Tier 2] or Electron [Tier 3]
  - Tauri for performance and security
  - Electron for complex Node.js integration
```

---

## Electron Guide

### Project Creation

```bash
# Create with electron-vite (recommended)
npm create @electron-vite/create my-electron-app
cd my-electron-app

# Install dependencies
npm install

# Start development server
npm run dev
```

### Folder Structure

```
my-electron-app/
├── src/
│   ├── main/               # Main process (Node.js)
│   │   └── index.ts        # App entry point, window management
│   ├── preload/            # Preload script
│   │   └── index.ts        # Renderer↔Main bridge
│   └── renderer/           # Renderer process (Web)
│       ├── src/            # React/Vue code
│       └── index.html      # HTML entry point
├── resources/              # App icons, assets
├── electron.vite.config.ts # Build configuration
├── electron-builder.yml    # Deployment configuration
└── package.json
```

### Core Concept: Process Separation

```
┌─────────────────────────────────────────────────────┐
│                    Electron App                      │
├─────────────────────────────────────────────────────┤
│  Main Process (Node.js)                             │
│  - System API access (files, network, etc.)         │
│  - Window creation/management                       │
│  - Menu, tray management                            │
├─────────────────────────────────────────────────────┤
│  Preload Script (Bridge)                            │
│  - Safe main↔renderer communication                 │
│  - Expose only specific APIs                        │
├─────────────────────────────────────────────────────┤
│  Renderer Process (Chromium)                        │
│  - Web UI (React, Vue, etc.)                        │
│  - DOM access                                       │
│  - No direct Node.js API access (security)          │
└─────────────────────────────────────────────────────┘
```

### Main Process

```typescript
// src/main/index.ts
import { app, BrowserWindow, ipcMain } from 'electron';
import { join } from 'path';

let mainWindow: BrowserWindow | null = null;

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      preload: join(__dirname, '../preload/index.js'),
      sandbox: false,
    },
  });

  // Dev mode: Load Vite server
  if (process.env.NODE_ENV === 'development') {
    mainWindow.loadURL('http://localhost:5173');
    mainWindow.webContents.openDevTools();
  } else {
    // Production: Load built files
    mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
  }
}

app.whenReady().then(createWindow);

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// IPC: Handle requests from renderer
ipcMain.handle('read-file', async (event, filePath) => {
  const fs = await import('fs/promises');
  return fs.readFile(filePath, 'utf-8');
});
```

### Preload Script

```typescript
// src/preload/index.ts
import { contextBridge, ipcRenderer } from 'electron';

// APIs to safely expose to renderer
contextBridge.exposeInMainWorld('electronAPI', {
  // Read file
  readFile: (filePath: string) => ipcRenderer.invoke('read-file', filePath),

  // Save file dialog
  saveFile: (content: string) => ipcRenderer.invoke('save-file', content),

  // App version
  getVersion: () => process.env.npm_package_version,

  // Platform
  platform: process.platform,
});

// Type definitions (for use in renderer)
declare global {
  interface Window {
    electronAPI: {
      readFile: (path: string) => Promise<string>;
      saveFile: (content: string) => Promise<void>;
      getVersion: () => string;
      platform: NodeJS.Platform;
    };
  }
}
```

### Renderer Process

```typescript
// src/renderer/src/App.tsx
import { useState } from 'react';

function App() {
  const [content, setContent] = useState('');

  const handleOpenFile = async () => {
    const result = await window.electronAPI.readFile('/path/to/file.txt');
    setContent(result);
  };

  return (
    <div className="app">
      <h1>My Electron App</h1>
      <p>Platform: {window.electronAPI.platform}</p>
      <button onClick={handleOpenFile}>Open File</button>
      <pre>{content}</pre>
    </div>
  );
}

export default App;
```

### Creating Menus

```typescript
// src/main/menu.ts
import { Menu, app, shell } from 'electron';

const template: Electron.MenuItemConstructorOptions[] = [
  {
    label: 'File',
    submenu: [
      { label: 'New File', accelerator: 'CmdOrCtrl+N', click: () => {} },
      { label: 'Open', accelerator: 'CmdOrCtrl+O', click: () => {} },
      { type: 'separator' },
      { label: 'Quit', role: 'quit' },
    ],
  },
  {
    label: 'Edit',
    submenu: [
      { label: 'Undo', role: 'undo' },
      { label: 'Redo', role: 'redo' },
      { type: 'separator' },
      { label