メインコンテンツまでスキップ

新機能の追加

このガイドでは、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

  1. routers/index.ts 内の registerAllRouters() で Service インスタンスと Router インスタンスを作成する
  2. router.register() を呼び出す
  3. preload/index.tsapi オブジェクトにメソッドを公開する

フェーズ 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-1text-foreground)を使用し、ハードコードされた色は使用しない
  • プロジェクトの UI コンポーネント(ButtonInputSelect)を使用し、ネイティブ 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 行
カスタム Hook300 行400 行600 行
ユーティリティ関数150 行200 行300 行
型定義100 行150 行200 行
Service300 行400 行600 行

警告ラインを超えたファイルは現在のタスク内で分割する必要があります。後回しにしてはいけません。

TypeScript

npm run typecheck

フェーズ 5:テスト

テストコマンド

コマンド説明
npm run testVitest のフルテスト実行
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-index Skill が更新されている
  • elfi-kb ナレッジベースが更新されている(ユーザーに見える機能を追加した場合)
  • PR の説明に変更内容が明確に記載されている