新機能の追加
このガイドでは、Elftia に完全な機能を実装するためのエンドツーエンドのプロセスについて説明します。IPC ルートの追加 をベースに、計画、テスト、ドキュメント更新を含む完全なライフサイクルをカバーします。
概要:機能開発フロー
計画 → バックエンド実装 → フロントエンド実装 → 品質チェック → テスト → ドキュメント更新
flowchart LR
A[Planning] --> B[Backend]
B --> C[Frontend]
C --> D[Quality Check]
D --> E[Testing]
E --> F[Docs Update]
F --> G[Submit PR]
フェーズ 1:計画
コードを書き始める前に、以下の質問に明確に答えてください。
要件分析チェックリスト
| 質問 | 目的 |
|---|---|
| 必要な共有型は何か? | @shared/contracts/ 配下の型ファイルを決定する |
| データベーステーブル操作を伴うか? | 新しいマイグレーションが必要かどうかを決定する |
| 必要な Service ロジックは何か? | バックエンドモジュールを決定する |
| 必要な IPC チャンネルは何か? | Router と Preload API を決定する |
| 必要な UI コンポーネントは何か? | フロントエンドのファイル構成を決定する |
| 新しいルーティングページを追加するか? | 遅延読み込みとルート登録が必要かどうかを決定する |
| ユーザーに見える機能か? | ナレッジベースのドキュメント更新が必要かどうかを決定する |
ファイル計画テンプレート
「Notes(メモ)」機能を例として:
packages/
├── desktop/app/
│ ├── shared/contracts/
│ │ └── note-types.ts # Shared types
│ └── main/
│ ├── services/content/workspace/notes/
│ │ ├── NoteFileService.ts # File CRUD service
│ │ └── NoteWatcher.ts # File watcher service
│ └── services/routers/
│ └── NoteRouter.ts # IPC Router
│
├── renderer/src/
│ ├── features/notes/
│ │ ├── hooks/
│ │ │ └── useNotes.ts # Data fetching Hook
│ │ └── components/
│ │ ├── NotesList.tsx # List component
│ │ ├── NoteEditor.tsx # Editor component
│ │ └── index.ts # Barrel export
│ └── locales/
│ ├── en/notes.json
│ ├── zh/notes.json
│ └── ja/notes.json
フェーズ 2:バックエンド実装
以下の順序でバックエンドコードを作成します。
2.1 共有型
{/* packages/desktop/app/shared/contracts/note-types.ts */}
export interface Note {
id: string;
title: string;
content: string;
tags: string[];
createdAt: string;
updatedAt: string;
}
export interface CreateNoteInput {
title: string;
content?: string;
tags?: string[];
}
2.2 Service
Service はすべてのビジネスロジックをカプセル化します。単一責任の原則に従い、ファイルの読み書きとファイル監視は別々の Service として分割します。
services/content/workspace/notes/
├── NoteFileService.ts # File CRUD operations
└── NoteWatcher.ts # File system change watching
2.3 Router
secureHandle パターンを使用して IPC ハンドラーを登録します。すべてのパラメータは Zod バリデーションを通過する必要があります。
詳細な手順は IPC ルートの追加 を参照してください。
2.4 登録 + Preload
routers/index.ts内のregisterAllRouters()で Service インスタンスと Router インスタンスを作成するrouter.register()を呼び出すpreload/index.tsのapiオブジェクトにメソッドを公開する
フェーズ 3:フロントエンド実装
3.1 Hook の作成
データ取得と状態管理のロジックをカスタム Hook にカプセル化します。
{/* packages/renderer/src/features/notes/hooks/useNotes.ts */}
export function useNotes() {
const [notes, setNotes] = useState<Note[]>([]);
const [loading, setLoading] = useState(true);
// Data fetching, CRUD operations...
return { notes, loading, create, update, remove };
}
3.2 コンポーネントの作成
コンポーネントは UI レンダリングのみを担当し、ビジネスロジックは Hook に委譲します。
components/notes/
├── index.ts # Barrel export
├── NotesList.tsx # List view (< 400 lines)
├── NoteEditor.tsx # Editor (< 400 lines)
└── NoteCard.tsx # Single note card
コンポーネント規約:
- セマンティックトークン(
bg-surface-1、text-foreground)を使用し、ハードコードされた色は使用しない - プロジェクトの UI コンポーネント(
Button、Input、Select)を使用し、ネイティブ HTML コントロールは使用しない - テキストの国際化には
useTranslation()を使用する
3.3 ルート登録(新しいページの場合)
機能に独立したページが必要な場合:
{/* packages/renderer/src/app/lazy/pages.tsx */}
export const LazyNotesPage = lazy(() => import('../../features/notes/NotesPage'));
export const SuspenseNotesPage = withSuspense(LazyNotesPage, PageSkeleton);
{/* packages/renderer/src/app/App.tsx — old import paths remain valid via the lazy/index.ts barrel re-export */}
import { SuspenseNotesPage as NotesPage } from './lazy';
<Route path="/notes" element={<NotesPage />} />
新しいページではなく新しい ワークスペースタイプ を追加する場合は別の手順になります:
features/chat/components/content/workspaces/registry.tsを編集し、definitions/<Name>Workspace.tsxを追加し、app/lazy/workspaces.tsxに対応する lazy import を追加します。詳細はpackages/renderer/CLAUDE.mdの「新しいワークスペースタイプの追加」セクションを参照してください。
3.4 i18n
3 言語すべての翻訳ファイルを作成し、i18n/index.ts に名前空間を登録します。
locales/
├── en/notes.json
├── zh/notes.json
└── ja/notes.json
フェーズ 4:品質チェック
ESLint
# Check modified files
npx eslint packages/renderer/src/features/notes/components/ packages/renderer/src/features/notes/hooks/
# Auto-fix import ordering, etc.
npx eslint packages/renderer/src/features/notes/components/ --fix
ファイルサイズの制限
| ファイルの種類 | 推奨 | 警告ライン | ハード上限 |
|---|---|---|---|
| React コンポーネント | 400 行 | 600 行 | 800 行 |
| カスタム Hook | 300 行 | 400 行 | 600 行 |
| ユーティリティ関数 | 150 行 | 200 行 | 300 行 |
| 型定義 | 100 行 | 150 行 | 200 行 |
| Service | 300 行 | 400 行 | 600 行 |
警告ラインを超えたファイルは現在のタスク内で分割する必要があります。後回しにしてはいけません。
TypeScript
npm run typecheck
フェーズ 5:テスト
テストコマンド
| コマンド | 説明 |
|---|---|
npm run test | Vitest のフルテスト実行 |
npm run test -- --filter notes | キーワードでテストをフィルタリング |
手動テストチェックリスト
- 機能のハッピーパスが正常に動作する
- エラーケースで正しいメッセージが表示される(ネットワークエラー、データベースエラーなど)
- ダークモードとライトモードで UI が正しく表示される
- 壁紙透過モードが有効な場合に UI が正しく表示される
- ウィンドウを最小幅に縮小してもレイアウトが崩れない
フェーズ 6:ドキュメントの更新
必須のドキュメント更新
| 条件 | 更新が必要なドキュメント |
|---|---|
| Service / コンポーネント / Hook / Context を追加した | .claude/skills/architecture-index/SKILL.md |
| ユーザーに見えるページ機能を追加した | docs/elfi-kb/ ナレッジベースのドキュメント |
| ページルートを追加した | docs/elfi-kb/INDEX.md のルートマップ |
| IPC インターフェースを変更した | このドキュメントサイトの IPC チャンネルリスト |
よく使うパターンの参考
パターン 1: CRUD Service
エンティティ管理機能(メモ、お気に入り、テンプレートなど)に適しています。
共有型 → Service(CRUD) → Router(Zod バリデーション) → Preload → Hook(useState+CRUD) → リスト + 編集コンポーネント
パターン 2: ストリーミングイベント
リアルタイムプッシュが必要な機能(チャット、生成タスクなど)に適しています。
バックエンド Service がイベントを発行 → メインプロセス webContents.send(channel, data) → Preload の onXxx リスナー → Hook でサブスクライブ/アンサブスクライブ
{/* Preload side — event subscription pattern */}
onEvent: (cb: (data: any) => void) => {
const channel = 'myFeature:event';
const handler = (_event: IpcRendererEvent, data: any) => cb(data);
ipcRenderer.on(channel, handler);
return () => ipcRenderer.removeListener(channel, handler);
},
{/* Hook side — subscription management */}
useEffect(() => {
const unsubscribe = window.api.myFeature.onEvent((data) => {
setMessages((prev) => [...prev, data]);
});
return unsubscribe;
}, []);
パターン 3: ファイル操作
ユーザーファイルの読み書きが必要な機能(プロジェクト管理、エクスポートなど)に適しています。
フロントエンドがファイルパス/設定を渡す → バックエンド Service がファイルシステムを操作する → 結果の URL またはコンテンツを返す
重要な原則: すべてのファイルシステム操作はバックエンドで完結します。フロントエンドはパスとパラメータを渡すだけで、ファイルを直接操作しません。
完全チェックリスト
バックエンド
- 共有型が
@shared/contracts/に定義されている - Service がすべてのビジネスロジックをカプセル化している
- Router が
secureHandle+ Zod バリデーションを使用している - Router が
registerAllRouters()に登録されている - Preload API のメソッドシグネチャが正しい
-
DesktopApi型インターフェースが更新されている
フロントエンド
- カスタム Hook がデータ取得ロジックをカプセル化している
- コンポーネントがセマンティックトークンとプロジェクトの UI コンポーネントを使用している
- 新しいページが
React.lazyによる遅延読み込みを使用している - 3 言語すべての i18n ファイルが作成されている
品質
-
npm run lintがエラーなしで通過する -
npm run typecheckが通過する - ファイルサイズが制限内に収まっている
- ダーク/ライトモードのテストが通過する
- 壁紙透過モードのテストが通過する
ドキュメント
-
architecture-indexSkill が更新されている -
elfi-kbナレッジベースが更新されている(ユーザーに見える機能を追加した場合) - PR の説明に変更内容が明確に記載されている