Electron UI Patterns
概要
ElectronデスクトップアプリケーションのUI実装パターンと設計知識を提供。 BrowserWindow管理、ネイティブUI要素の活用、フレームレスウィンドウの実装を支援する。
ワークフロー
Phase 1: UI要件の整理
目的: 実装対象のUI要素を特定
アクション:
- 実装対象を特定(BrowserWindow、メニュー、ダイアログ)
references/で対応するパターンを確認- プロジェクト要件に合致するパターンを選定
Phase 2: UI実装
目的: UIパターンに従って実装
アクション:
- 該当する
agents/のTask仕様書を参照 assets/frameless-window.tsなどテンプレートを活用- BrowserWindow設定、スタイリング、イベントハンドリングを実装
- ネイティブUI要素(メニュー、ダイアログ、トレイ)を統合
Phase 3: 検証と記録
目的: 動作検証と記録
アクション:
- ウィンドウ表示、メニュー動作をテスト
scripts/log_usage.mjsで記録
Task仕様ナビ
| Task | 起動タイミング | 入力 | 出力 |
|---|---|---|---|
| browserwindow-setup | BrowserWindow設定時 | プロジェクト要件 | 初期化コード・preload |
| custom-titlebar | カスタムタイトルバー実装時 | デザイン仕様 | フレームレス設定・CSS |
| native-menu | ネイティブメニュー実装時 | 機能要件 | メニュー構築コード |
| system-tray | システムトレイ設定時 | アイコン画像 | Tray設定コード |
詳細仕様: 各Taskの詳細は agents/ ディレクトリの対応ファイルを参照
ベストプラクティス
すべきこと
- コンテキスト分離: preload.jsでコンテキストを分離
- BrowserWindow最小化: 必要最小限の機能のみ有効化
- IPC通信: メインとレンダラーの分離
- ネイティブUI活用: プラットフォーム固有のMenu/Dialog/Tray
- 状態永続化: ウィンドウ位置・サイズを保存
避けるべきこと
- nodeIntegration有効化: セキュリティリスク
- enableRemoteModule使用: 直接APIアクセスは避ける
- synchronous IPC: 非同期通信を使用
- プラットフォーム差異無視: Windows/macOS/Linuxの違いを考慮
リソース参照
references/(詳細知識)
| リソース | パス | 用途 |
|---|---|---|
| ネイティブUI | See references/native-ui.md | メニュー・ダイアログ |
| ウィンドウ管理 | See references/window-management.md | BrowserWindow詳細 |
| エディタ統合 | See references/editor-integration.md | EditorInstance・検索統合 |
scripts/(決定論的処理)
| スクリプト | 用途 | 使用例 |
|---|---|---|
log_usage.mjs |
フィードバック記録 | node scripts/log_usage.mjs --result success --phase "Phase 3" |
validate-skill.mjs |
スキル構造検証 | node scripts/validate-skill.mjs --verbose |
assets/(テンプレート)
| テンプレート | 用途 |
|---|---|
frameless-window.ts |
フレームレスウィンドウテンプレート |
実装事例(2026-01-06追加)
EditorView検索統合リファクタリング
search-replace-ui-implementation Phase 10で、ElectronアプリのEditorViewコンポーネントに検索・置換機能を統合した事例。
背景と要件
デスクトップアプリのエディタビューに以下の検索機能を統合する必要があった:
- ファイル内検索・置換(Cmd+F / Cmd+T)
- ワークスペース全体検索・置換(Cmd+Shift+F / Cmd+Shift+T)
- ファイル名検索(Cmd+P)
課題として、検索機能はElectron IPC経由でメインプロセスと通信する必要があり、エディタ操作(ハイライト、スクロール、置換)とも連携が必要だった。
適用したパターン
EditorInstanceアダプターパターン
異なるエディタ実装(TextArea、Monaco Editor、CodeMirror)を統一APIで操作するためのアダプターパターンを適用。
EditorInstanceインターフェースを定義
- getContent(): エディタ内容を取得
- scrollToLine(line, column): 指定位置にスクロール
- replaceText(line, column, length, replacement): 単一箇所を置換
- replaceAllText(matches, replacement): 複数箇所を一括置換
- focus(): エディタにフォーカス
TextAreaEditorAdapterを実装
- HTMLTextAreaElementをEditorInstanceインターフェースでラップ
- 文字位置から行・列への変換ロジックを内包
- 将来Monaco Editorに移行時はMonacoEditorAdapterを実装するだけで対応可能
IPC検索プロバイダーパターン
Electron IPC通信をカプセル化し、検索パネルがIPC詳細を知らなくても検索を実行できるようにした。
- WorkspaceSearchProviderをAsyncGeneratorとして定義
- ファイル単位で検索結果をyieldし、UIが段階的に結果を表示可能
- EditorView側でuseWorkspaceSearchフックとして提供
- 検索パネルはプロバイダーを受け取るだけで、IPC通信の詳細は隠蔽
キーボードショートカット統合パターン
エディタと検索パネル間のキーボード操作を一元管理。
- useSearchKeyboardShortcutsフックでイベントハンドリングを集約
- 検索モード(file/workspace/filename)に応じた動作の切り替え
- パネル表示中はパネル内でのキー操作を優先(Escapeでパネル閉じる等)
Electron固有の考慮事項
IPC通信のオーバーヘッド
- 検索はメインプロセスで実行し、結果をレンダラーに返却
- 大量結果時はストリーミングで段階的に返却
コンテキスト分離
- preload.jsで公開するAPIを最小限に
- 検索APIはelectronAPI.search.executeWorkspaceとして公開
フォーカス管理
- 検索パネル表示時にフォーカスを検索入力に移動
- パネル閉じる時にエディタにフォーカスを戻す
成果
- EditorView: 713行 → 495行(約30%削減)
- 検索機能を独立したfeatureモジュール(apps/desktop/src/features/search/)として分離
- エディタ実装変更時の影響をアダプター層に局所化
- IPC通信のモック化によりテスト容易性向上
IPC通信エラーハンドリングパターン(2026-01-10追加)
CONV-05-03(履歴/ログ表示UIコンポーネント)で適用したIPC通信のエラーハンドリングパターン。
パターン概要
Electron IPC通信において、以下のエラー状況に対応するための統一的なパターン。
| エラー種別 | 原因 | 対応 |
|---|---|---|
| API未利用可能 | preload未設定、contextBridge未公開 | 明示的なエラーメッセージ |
| IPC通信失敗 | チャンネル未登録、タイムアウト | 再試行機能付きエラー表示 |
| データ取得失敗 | DBエラー、ファイル不在 | Result型でエラー情報を伝播 |
Result型パターン
IPC通信の結果を型安全に扱うためのパターン。
// 成功/失敗を型で区別
interface SuccessResult<T> {
success: true;
data: T;
}
interface ErrorResult {
success: false;
error: Error;
}
type Result<T> = SuccessResult<T> | ErrorResult;
// 使用例:IPC応答の処理
const result = await window.historyAPI.getFileHistory(fileId, options);
if (result.success) {
setHistory(result.data.items);
} else {
setError(result.error);
}
API利用可能性チェックパターン
preloadスクリプトでAPIが正しく公開されているかを確認。
function useVersionHistory(fileId: string) {
const [error, setError] = useState<Error | null>(null);
const fetchHistory = useCallback(async () => {
// API利用可能性チェック
if (!window.historyAPI) {
setError(new Error("History API not available"));
setIsLoading(false);
return;
}
try {
const result = await window.historyAPI.getFileHistory(fileId);
// ...
} catch (err) {
setError(err instanceof Error ? err : new Error(String(err)));
}
}, [fileId]);
}
再試行機能付きエラー表示パターン
エラー発生時にユーザーが再試行できるUIを提供。
function ErrorDisplay({
message,
onRetry,
}: {
message: string;
onRetry: () => void;
}) {
return (
<div role="alert" className="rounded-lg bg-red-50 p-4 text-center">
<p className="mb-3 text-red-700">エラーが発生しました: {message}</p>
<button
type="button"
className="rounded-lg bg-red-600 px-4 py-2 text-sm font-medium text-white"
>
再試行
</button>
</div>
);
}
// 使用例:履歴一覧でのエラーハンドリング
if (error && history.length === 0) {
return <ErrorDisplay message={error.message} />;
}
Window型拡張パターン
TypeScriptでwindowオブジェクトの拡張を型安全に行う。
// types.tsで型を定義
interface HistoryAPI {
getFileHistory(
fileId: string,
options?: PaginationOptions,
): Promise<Result<PaginatedResult>>;
// ...
}
declare global {
interface Window {
historyAPI?: HistoryAPI; // ?でオプショナルに
}
}
// コンポーネントでの使用
if (window.historyAPI) {
const result = await window.historyAPI.getFileHistory(fileId);
}
エラー種別の分類
| エラークラス | 表示メッセージ | 再試行 |
|---|---|---|
| NetworkError | 「通信エラーが発生しました」 | 可 |
| NotFoundError | 「データが見つかりません」 | 不可 |
| RestoreError | 「復元に失敗しました」 | 可 |
| DBError | 「データベースエラーが発生しました」 | 可 |
実装詳細
詳細は以下を参照:
- 実装例:
apps/desktop/src/renderer/hooks/useVersionHistory.ts - 型定義:
apps/desktop/src/renderer/components/history/types.ts - 仕様書:
.claude/skills/aiworkflow-requirements/references/ui-ux-history-panel.md
変更履歴
| Version | Date | Changes |
|---|---|---|
| 2.4.0 | 2026-01-10 | IPC通信エラーハンドリングパターン追加(CONV-05-03) |
| 2.3.0 | 2026-01-06 | validate-skill.mjs追加 |
| 2.2.0 | 2026-01-06 | editor-integration.md追加、skill-creator準拠 |
| 2.1.0 | 2026-01-06 | EditorView検索統合パターン追加 |
| 2.0.0 | 2026-01-01 | 18-skills.md仕様完全準拠、構造最適化 |
| 1.0.0 | 2025-12-31 | 初版作成 |