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

9.6 KiB
Raw Blame History

Итерация 5: Git status, file watching, расширенные возможности

Зависит от: Итерация 4

Цель

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 конфигурация

// 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 конфигурация

// 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.