agent-ecosystem/docs/iterations/edit-project/iter-5-git-watching.md
iliya 0cb85d463c feat: enhance project editor with new error boundary and file handling improvements
- Introduced `EditorErrorBoundary` component to catch runtime errors in CodeMirror, providing a fallback UI to prevent crashes.
- Updated file handling logic to utilize `isbinaryfile` for more reliable binary detection, replacing manual null-byte scans.
- Enhanced `openFile` method to prevent duplicate tabs for already opened files.
- Improved documentation for new components and updated file lists to reflect recent changes.
- Optimized file watcher and path validation processes for better performance and reliability.
2026-02-28 17:55:21 +02:00

154 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Итерация 5: Git status, file watching, расширенные возможности
> Зависит от: [Итерация 4](iter-4-search-shortcuts.md)
## Цель
Git status в дереве файлов. Live refresh при изменениях на диске. Conflict detection при сохранении. Line wrap toggle.
## Новые npm-зависимости
- `simple-git` v3.32+ (`pnpm add simple-git`) — обёртка над git CLI с TypeScript типами, parsed StatusResult, встроенным timeout/abort. 7.9M downloads/нед, dual ESM/CJS, не native module
- `chokidar` v4.0.3 (`pnpm add chokidar@4`) — file watcher (117M downloads/нед). Решает все проблемы raw `fs.watch`: нормализация событий, recursive на Linux, ENOSPC handling, symlinks. Dual CJS/ESM, Node 14+
## IPC каналы
| Канал | Описание |
|-------|----------|
| `editor:gitStatus` | Git status через `simple-git` (v3.32+), кеш 5 сек |
| `editor:watchDir` | Запуск file watcher (opt-in, НЕ по умолчанию) |
| `editor:change` | Event: файл изменился на диске (main -> renderer) |
## Новые файлы
| # | Файл | Описание |
|---|------|----------|
| 1 | `src/main/services/editor/EditorFileWatcher.ts` | FileWatcher через `chokidar` v4 (~50-70 LOC). Burst coalescing, ENOSPC, recursive Linux — всё встроено. Фильтрация node_modules/.git/dist через `ignored` option |
| 2 | `src/main/services/editor/GitStatusService.ts` | Git status через `simple-git` с `StatusResult` маппингом, кеш 5 сек. Переиспользовать `isGitRepo()` из `GitDiffFallback.ts` (~80-100 LOC) |
| 3 | `src/main/services/editor/conflictDetection.ts` | Утилита mtime check: сравнение mtime до/после save, conflict resolution (~40 LOC) |
| 4 | `src/renderer/components/team/editor/GitStatusBadge.tsx` | M/U/A бейджи в дереве |
## Изменения в существующих файлах
| # | Файл | Изменение |
|---|------|-----------|
| 1 | `src/shared/types/editor.ts` | `GitFileStatus`, `EditorFileChangeEvent` |
| 2 | `src/shared/types/api.ts` | `gitStatus`, `onEditorChange` в EditorAPI |
| 3 | `src/main/ipc/editor.ts` | Handlers для git status и file watcher |
| 4 | `src/preload/index.ts` | `editor.gitStatus`, `editor.onEditorChange` (НЕ `onFileChange` — конфликт с существующим `ElectronAPI.onFileChange`) |
| 5 | `src/preload/constants/ipcChannels.ts` | `EDITOR_GIT_STATUS`, `EDITOR_WATCH_DIR`, `EDITOR_CHANGE` |
| 6 | `src/renderer/components/team/editor/EditorFileTree.tsx` | Git status badges |
| 7 | `src/renderer/components/team/editor/CodeMirrorEditor.tsx` | Conflict detection (mtime check) при сохранении |
| 8 | `src/renderer/components/team/editor/ProjectEditorOverlay.tsx` | File watcher подписка, auto-refresh, conflict modal |
| 9 | `src/renderer/store/slices/editorSlice.ts` | Git status data, file watcher state |
| 10 | `src/renderer/store/index.ts` | В `initializeNotificationListeners()` добавить подписку `if (api.editor?.onEditorChange)` → обновление дерева/табов при внешних изменениях (guard обязателен — паттерн из всех существующих subscriptions) |
| 11 | `src/main/index.ts` | `mainWindow.on('closed')``cleanupEditorState()`. `shutdownServices()``cleanupEditorState()` |
## Security-требования
1. `editor:gitStatus`: `cwd = activeProjectRoot` (валидный). Не передавать full paths от git без валидации
2. `editor:change`: пути в events могут утечь через symlink -- валидировать перед передачей в renderer (SEC-2)
## Watcher lifecycle cleanup (macOS: window closed but app alive)
- `editor:open` — если `activeProjectRoot !== null`, сначала остановить предыдущий watcher и сбросить state (идемпотентный reset). Guard: `if (activeProjectRoot !== null) throw new Error('Another editor is already open')`
- `mainWindow.on('closed')` в `src/main/index.ts` — вызвать `cleanupEditorState()` (экспорт из `editor.ts`): сброс `activeProjectRoot`, остановка watcher. Аналог существующего cleanup для `notificationManager`, `ptyTerminalService`
- `shutdownServices()` — добавить `cleanupEditorState()` рядом с `removeIpcHandlers()`
## Performance-требования (R4/R5)
- File watcher opt-in: по умолчанию ВЫКЛЮЧЕН. Toggle "Watch for external changes". По умолчанию ручной refresh (F5)
### chokidar конфигурация
```typescript
// src/main/services/editor/EditorFileWatcher.ts
import { watch, type FSWatcher } from 'chokidar';
let watcher: FSWatcher | null = null;
function startWatching(projectRoot: string, onChange: (event: EditorFileChangeEvent) => void) {
watcher = watch(projectRoot, {
ignored: /(node_modules|\.git|dist|__pycache__|\.cache|\.next|\.venv|\.tox|vendor)/,
ignoreInitial: true,
followSymlinks: false,
depth: 20,
});
watcher.on('change', path => onChange({ type: 'change', path }));
watcher.on('add', path => onChange({ type: 'create', path }));
watcher.on('unlink', path => onChange({ type: 'delete', path }));
}
function stopWatching() {
watcher?.close();
watcher = null;
}
```
- **chokidar v4** решает все проблемы raw `fs.watch`: нормализация событий macOS, recursive на Linux, ENOSPC handling, debounce
- **macOS**: FSEvents через chokidar (надёжность 9/10)
- **Linux**: inotify через chokidar с автоматическим fallback (надёжность 8/10, было 6/10 с raw fs.watch)
- Git status кешировать на 5 секунд. Invalidate по file watcher event
### simple-git конфигурация
```typescript
// src/main/services/editor/GitStatusService.ts
import { simpleGit, StatusResult, SimpleGit } from 'simple-git';
// Создать инстанс с --no-optional-locks + timeout
const createGit = (projectRoot: string): SimpleGit =>
simpleGit({
baseDir: projectRoot,
timeout: { block: 10_000 }, // 10s (паттерн из GitDiffFallback.ts)
}).env('GIT_OPTIONAL_LOCKS', '0'); // эквивалент --no-optional-locks
// Маппинг StatusResult → GitFileStatus[]
function mapStatus(result: StatusResult): GitFileStatus[] {
const files: GitFileStatus[] = [];
for (const p of result.modified) files.push({ path: p, status: 'modified' });
for (const p of result.not_added) files.push({ path: p, status: 'untracked' });
for (const p of result.staged) files.push({ path: p, status: 'staged' });
for (const p of result.deleted) files.push({ path: p, status: 'deleted' });
for (const p of result.conflicted) files.push({ path: p, status: 'conflict' });
for (const r of result.renamed) files.push({ path: r.to, status: 'renamed', renamedFrom: r.from });
return files;
}
```
- **`GIT_OPTIONAL_LOCKS=0`** — предотвращает `.git/index.lock` конфликты (критично для фоновых запросов!)
- **`timeout.block: 10_000`** — SIGINT после 10 сек без вывода
- **Парсинг не нужен** — `simple-git` делает полный парсинг porcelain вывода включая renamed, conflicts, ahead/behind
- Переиспользовать `isGitRepo()` из `GitDiffFallback.ts` для проверки наличия `.git`
- Graceful degradation:
- Нет git → скрыть git бейджи, "Git not available" в status bar
- Не git-repo → скрыть git бейджи
- Timeout → "Git status unavailable" + кнопка retry
## UX-требования
- File changed on disk while open: banner в табе "File changed on disk. [Reload] [Keep mine] [Show diff]" (НЕ перезаписывать молча)
- File deleted on disk while open: banner "File no longer exists on disk. [Close tab]"
- Conflict detection при save: mtime check. Если изменился -- dialog "Overwrite / Cancel / Show diff"
- Line wrap toggle в toolbar
## Тестирование
| # | Что тестировать | Файл |
|---|----------------|------|
| 1 | `GitStatusService` -- маппинг `simple-git` StatusResult → GitFileStatus[], кеш, graceful degradation | `test/main/services/editor/GitStatusService.test.ts` |
| 2 | `EditorFileWatcher` -- chokidar watcher lifecycle (start/stop), event mapping, cleanup | `test/main/services/editor/EditorFileWatcher.test.ts` |
| 3 | `conflictDetection` -- mtime check логика | `test/main/services/editor/conflictDetection.test.ts` |
| 4 | Manual: изменить файл в внешнем редакторе -> conflict banner | — |
## Критерии готовности
- [ ] Git status бейджи (M/U/A/C) в файловом дереве (C = conflict для UU/AA/DD)
- [ ] Auto-refresh при изменениях на диске (при включённом watcher)
- [ ] Conflict detection при сохранении
- [ ] Line wrap toggle
## Оценка
- **Надёжность решения: 9/10** (было 8/10) -- `simple-git` + `chokidar` убирают ~300 LOC ручного кода: парсинг porcelain, fs.watch нормализация, ENOSPC fallback. Всё покрыто проверенными пакетами.
- **Уверенность: 9/10** -- simple-git 7.9M + chokidar 117M downloads/нед. Оба с TypeScript, проверены в production.