Добавление новой функции
В этом руководстве описан сквозной процесс реализации полноценной функции в Elftia. Оно дополняет Добавление IPC-маршрутов и охватывает полный жизненный цикл: планирование, тестирование и обновление документации.
Обзор: процесс разработки функции
Планирование → Бэкенд → Фронтенд → Проверка качества → Тестирование → Обновление документации
flowchart LR
A[Планирование] --> B[Бэкенд]
B --> C[Фронтенд]
C --> D[Проверка качества]
D --> E[Тестирование]
E --> F[Обновление документации]
F --> G[Отправить PR]
Фаза 1: Планирование
Прежде чем писать код, чётко ответьте на следующие вопросы:
Контрольный список анализа требований
| Вопрос | Цель |
|---|---|
| Какие общие типы необходимы? | Определяет файлы типов в @shared/contracts/ |
| Затрагиваются ли операции с таблицами базы данных? | Определяет, нужна ли новая миграция |
| Какая логика Service нужна? | Определяет модули бэкенда |
| Какие IPC-каналы нужны? | Определяет Router и Preload API |
| Какие UI-компоненты нужны? | Определяет структуру файлов фронтенда |
| Добавляется ли новая страница с маршрутом? | Определяет, нужна ли ленивая загрузка и регистрация маршрута |
| Видит ли пользователь эту функцию? | Определяет, нужно ли обновлять документацию базы знаний |
Шаблон планирования файлов
На примере функции «Заметки»:
packages/
├── desktop/app/
│ ├── shared/contracts/
│ │ └── note-types.ts # Общие типы
│ └── main/
│ ├── services/content/workspace/notes/
│ │ ├── NoteFileService.ts # Сервис файловых CRUD-операций
│ │ └── NoteWatcher.ts # Сервис наблюдения за файлами
│ └── services/routers/
│ └── NoteRouter.ts # IPC Router
│
├── renderer/src/
│ ├── features/notes/
│ │ ├── hooks/
│ │ │ └── useNotes.ts # Hook для получения данных
│ │ └── components/
│ │ ├── NotesList.tsx # Компонент списка
│ │ ├── NoteEditor.tsx # Компонент редактора
│ │ └── index.ts # Barrel-экспорт
│ └── 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 # Файловые CRUD-операции
└── NoteWatcher.ts # Наблюдение за изменениями файловой системы
2.3 Router
Регистрируйте IPC-обработчики с помощью паттерна secureHandle. Все параметры должны проходить валидацию через Zod.
Подробные шаги см. в Добавление IPC-маршрутов.
2.4 Регистрация + Preload
- Создайте экземпляры Service и Router в
registerAllRouters()внутриrouters/index.ts - Вызовите
router.register() - Откройте методы в объекте
apiвpreload/index.ts
Фаза 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);
// Получение данных, CRUD-операции...
return { notes, loading, create, update, remove };
}
3.2 Создание компонентов
Компоненты отвечают только за рендеринг UI; бизнес-логику делегируйте Hook:
components/notes/
├── index.ts # Barrel-экспорт
├── NotesList.tsx # Представление списка (< 400 строк)
├── NoteEditor.tsx # Редактор (< 400 строк)
└── NoteCard.tsx # Карточка одной заметки
Стандарты компонентов:
- Используйте семантические токены (
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 — старые пути импорта остаются действительными через barrel re-export в lazy/index.ts */}
import { SuspenseNotesPage as NotesPage } from './lazy';
<Route path="/notes" element={<NotesPage />} />
Чтобы добавить новый тип workspace (не новую страницу), следуйте другому пути: отредактируйте
features/chat/components/content/workspaces/registry.ts, добавьтеdefinitions/<Name>Workspace.tsxи соответствующий lazy-импорт вapp/lazy/workspaces.tsx. См. раздел «Добавление нового типа Workspace» вpackages/renderer/CLAUDE.md.
3.4 i18n
Создайте файлы переводов для всех трёх языков и зарегистрируйте пространство имён в i18n/index.ts:
locales/
├── en/notes.json
├── zh/notes.json
└── ja/notes.json
Фаза 4: Проверка качества
ESLint
# Проверить изменённые файлы
npx eslint packages/renderer/src/features/notes/components/ packages/renderer/src/features/notes/hooks/
# Автоматически исправить порядок импортов и т. д.
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: Потоковые события
Подходит для функций с push-уведомлениями в реальном времени (чат, задачи генерации и т. д.).
Service бэкенда генерирует событие → Main-процесс webContents.send(channel, data) → Preload onXxx-слушатель → Hook подписка/отписка
{/* Сторона Preload — паттерн подписки на события */}
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 — управление подпиской */}
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 - Файлы i18n созданы для всех трёх языков
Качество
-
npm run lintвыполняется без ошибок -
npm run typecheckпроходит - Размеры файлов в пределах лимитов
- Тесты тёмной/светлой темы пройдены
- Тесты режима прозрачности обоев пройдены
Документация
- Skill
architecture-indexобновлён - База знаний
elfi-kbобновлена (если добавлена видимая пользователю функция) - Описание PR чётко объясняет внесённые изменения