agent-ecosystem/docs/iterations/edit-project/file-list.md
iliya d7fc71a5e6 feat: enhance project editor with file management, Git integration, and UI improvements
- Introduced `EditorFileWatcher` for live file change detection and `GitStatusService` for displaying Git status in the file tree.
- Added context menu for file operations (create, delete) and implemented multi-tab support for the editor.
- Enhanced user experience with keyboard shortcuts, search functionality, and breadcrumb navigation.
- Updated IPC channels for file operations and integrated conflict detection during file saves.
- Improved performance with file watcher optimizations and virtualized file tree rendering.
2026-02-28 13:35:22 +02:00

137 lines
9.9 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.

# Риски, бенчмарки, полный список файлов
## Риски
| # | Риск | Вероятность | Импакт | Итерация | Митигация |
|---|------|------------|--------|----------|-----------|
| 1 | Path traversal через IPC | Средняя | Критический | 1+ | `validateFilePath()` на КАЖДОМ handler + module-level projectRoot |
| 2 | Symlink escape из projectRoot | Высокая | Критический | 1 | `fs.realpath()` + re-check на каждом entry в readDir |
| 3 | node_modules/огромные директории -- OOM | Высокая | Высокий | 1 | IGNORED_DIRS фильтр + MAX_DIR_ENTRIES=500 + виртуализация (итерация 4) |
| 4 | CM6 тормозит на файлах >2MB | Низкая | Средний | 1 | Hard limit 2MB + тиерная стратегия + external editor fallback |
| 5 | TOCTOU race condition при save | Высокая | Высокий | 2 | Atomic write (tmp + rename) + post-read verify |
| 6 | Race condition: агент и пользователь редактируют один файл | Высокая | Высокий | 5 | mtime check + conflict dialog (overwrite / cancel / diff) |
| 7 | Unsaved data loss при crash | Средняя | Средний | 2 | Возможен autosave в localStorage/IndexedDB (P2 фича) |
| 8 | Device file DoS (/dev/zero) | Средняя | Высокий | 1 | `fs.lstat()` + `isFile()` + block /dev/ /proc/ /sys/ |
| 9 | Credential leakage (.env, .key) | Высокая | Высокий | 1 | `validateFilePath()` + визуальная пометка + блокировка чтения |
| 10 | ReDoS в searchInFiles | Средняя | Средний | 4 | Только literal search + timeout через AbortController |
| 11 | Memory leak: 20+ EditorView | Высокая | Критический | 2 | EditorState pooling + LRU eviction |
| 12 | Zustand keystroke storm | Высокая | Высокий | 2 | Content вне store + debounced dirty flag |
| 13 | XSS через имена файлов | Низкая | Средний | 1 | React JSX + validateFileName() при создании |
| 14 | Запись в .git/ | Средняя | Высокий | 2 | `isGitInternalPath()` блокирует write |
| 15 | ~~review.ts без валидации пути~~ | ~~Существует~~ | ~~Критический~~ | **ИСПРАВЛЕНО** | `validateFilePath()` добавлен в handleSaveEditedFile (hotfix применён) |
---
## Benchmarks
```
Benchmark 1: EditorView memory
Открыть 25 файлов x 200KB
Измерить: performance.memory.usedJSHeapSize
Порог: < 150MB
Benchmark 2: Tab switch latency
Переключить таб (500KB файл с syntax highlighting)
Измерить: time from click to contentful paint
Порог: < 50ms
Benchmark 3: File tree render
5000+ файлов, все папки раскрыты (с виртуализацией)
Измерить: FPS при скролле
Порог: >= 55fps
Benchmark 4: readDir latency
Директория с 5000 файлами
Измерить: time from click to tree displayed
Порог: < 200ms
Benchmark 5: Keystroke re-renders
React DevTools Profiler при наборе текста
Порог: FileTreePanel и TabBar рендерятся 0 раз при наборе
```
---
## Полный список файлов
### Новые файлы (~30)
| # | Файл | Итерация | Описание |
|---|------|----------|----------|
| 1 | `src/shared/types/editor.ts` | 1 | Все типы editor |
| 2 | `src/main/services/editor/ProjectFileService.ts` | 1 | Stateless файловый сервис |
| 3 | `src/main/services/editor/index.ts` | 1 | Barrel export: `{ ProjectFileService }` (расширяется в итерациях 4-5) |
| 4 | `src/main/services/editor/FileSearchService.ts` | 4 | Search in files |
| 5 | `src/main/services/editor/GitStatusService.ts` | 5 | git status --porcelain |
| 6 | `src/main/services/editor/EditorFileWatcher.ts` | 5 | FileWatcher (~250-300 LOC, burst coalescing + ENOSPC fallback) |
| 7 | `src/main/services/editor/conflictDetection.ts` | 5 | Утилита mtime check: сравнение mtime до/после save, conflict resolution (~40 LOC) |
| 8 | `src/main/ipc/editor.ts` | 1 | IPC handlers |
| 9 | `src/main/ipc/ipcWrapper.ts` | 1 | Общий `createIpcWrapper()` |
| 10 | `src/main/utils/atomicWrite.ts` | 2 | Перемещение `atomicWriteAsync()` из `team/atomicWrite.ts` (randomUUID, fsync, EXDEV fallback) |
| 11 | `src/renderer/utils/fileTreeBuilder.ts` | 1 | buildTree (рефакторинг) |
| 12 | `src/renderer/utils/codemirrorLanguages.ts` | 1 | Языковой маппинг (рефакторинг) |
| 13 | `src/renderer/utils/codemirrorTheme.ts` | 1 | Базовая тема CM (рефакторинг) |
| 14 | `src/renderer/utils/tabLabelDisambiguation.ts` | 3 | Disambiguation дублей |
| 15 | `src/renderer/store/slices/editorSlice.ts` | 1 | Zustand slice (Группа 1: tree), расширяется в итерации 2-3 |
| 16 | `src/renderer/hooks/useEditorKeyboardShortcuts.ts` | 4 | Горячие клавиши |
| 17 | `src/renderer/components/common/FileTree.tsx` | 1 | Generic FileTree с render-props |
| 18 | `src/renderer/components/team/editor/ProjectEditorOverlay.tsx` | 1 | Full-screen overlay |
| 19 | `src/renderer/components/team/editor/EditorFileTree.tsx` | 1 | Обёртка над FileTree |
| 20 | `src/renderer/components/team/editor/CodeMirrorEditor.tsx` | 1 | CM6 wrapper |
| 21 | `src/renderer/components/team/editor/EditorTabBar.tsx` | 2 | Панель вкладок |
| 22 | `src/renderer/components/team/editor/EditorToolbar.tsx` | 2 | Toolbar |
| 23 | `src/renderer/components/team/editor/EditorStatusBar.tsx` | 2 | Status bar |
| 24 | `src/renderer/components/team/editor/EditorEmptyState.tsx` | 1 | Empty state |
| 25 | `src/renderer/components/team/editor/EditorBinaryState.tsx` | 1 | Binary файлы |
| 26 | `src/renderer/components/team/editor/EditorErrorState.tsx` | 1 | Ошибки чтения |
| 27 | `src/renderer/components/team/editor/EditorContextMenu.tsx` | 3 | Context menu |
| 28 | `src/renderer/components/team/editor/NewFileDialog.tsx` | 3 | Inline-input |
| 29 | `src/renderer/components/team/editor/QuickOpenDialog.tsx` | 4 | Cmd+P dialog |
| 30 | `src/renderer/components/team/editor/SearchInFilesPanel.tsx` | 4 | Cmd+Shift+F |
| 31 | `src/renderer/components/team/editor/EditorBreadcrumb.tsx` | 4 | Breadcrumb |
| 32 | `src/renderer/components/team/editor/EditorShortcutsHelp.tsx` | 4 | Shortcuts modal |
| 33 | `src/renderer/components/team/editor/fileIcons.ts` | 4 | Иконки файлов |
| 34 | `src/renderer/components/team/editor/GitStatusBadge.tsx` | 5 | M/U/A/C(conflict) бейджи |
| 35 | `src/renderer/utils/editorBridge.ts` | 2 | Module-level singleton: Store ↔ CM6 refs bridge (R3) |
### Модификации существующих файлов (~17)
| # | Файл | Итерация | Изменение |
|---|------|----------|-----------|
| 1 | `src/preload/constants/ipcChannels.ts` | 1-5 | +12 констант EDITOR_* (включая EDITOR_CLOSE) |
| 2 | `src/preload/index.ts` | 1-5 | Секция `editor: { ... }` |
| 3 | `src/shared/types/api.ts` | 1-5 | `EditorAPI` interface |
| 4 | `src/main/ipc/review.ts` | 1 | Замена wrapReviewHandler на import из ipcWrapper |
| 5 | `src/main/utils/pathValidation.ts` | 1 | +validateFileName, +isDevicePath, +isGitInternalPath |
| 6 | `src/renderer/store/types.ts` | 1 | +EditorSlice в AppState |
| 7 | `src/renderer/store/index.ts` | 1 | +createEditorSlice |
| 8 | `src/renderer/components/team/TeamDetailView.tsx` | 1 | Кнопка "Open in Editor" + overlay state |
| 9 | `src/renderer/components/team/review/ReviewFileTree.tsx` | 1 | Рефакторинг: generic FileTree + fileTreeBuilder |
| 10 | `src/renderer/components/team/review/CodeMirrorDiffView.tsx` | 1 | Рефакторинг: импорт из codemirrorLanguages/Theme |
| 11 | `src/main/ipc/handlers.ts` | 1 | +initializeEditorHandlers() + registerEditorHandlers(ipcMain) + removeEditorHandlers(ipcMain) |
| 12 | `src/renderer/api/httpClient.ts` | 1 | Stub для editor: EditorAPI (throw "not available in browser mode") |
| 13 | `src/main/ipc/teams.ts` | follow-up | Миграция wrapTeamHandler → createIpcWrapper (40+ замен, отдельный PR) |
| 14 | `src/shared/types/index.ts` | 1 | +`export type * from './editor'` (barrel re-export, паттерн как team/review/terminal) |
| 15 | `src/main/index.ts` | 5 | `mainWindow.on('closed')``cleanupEditorState()`. `shutdownServices()``cleanupEditorState()` |
| 16 | `src/renderer/index.css` | 2 | +editor CSS-переменные |
| 17 | `src/renderer/hooks/useKeyboardShortcuts.ts` | 4 | Guard `editorOpen` для 6 конфликтующих shortcuts (R1) |
### Тесты (новые, ~15)
| # | Файл | Итерация |
|---|------|----------|
| 1 | `test/main/services/editor/ProjectFileService.test.ts` | 1 |
| 2 | `test/main/ipc/editor.test.ts` | 1 |
| 3 | `test/main/ipc/ipcWrapper.test.ts` | 1 |
| 4 | `test/main/utils/atomicWrite.test.ts` | 2 |
| 5 | `test/renderer/utils/fileTreeBuilder.test.ts` | 1 |
| 6 | `test/renderer/utils/codemirrorLanguages.test.ts` | 1 |
| 7 | `test/renderer/store/editorSlice.test.ts` | 1 (расширяется в 2-3) |
| 8 | `test/renderer/utils/tabLabelDisambiguation.test.ts` | 3 |
| 9 | `test/renderer/components/team/editor/EditorContextMenu.test.ts` | 3 |
| 10 | `test/main/services/editor/FileSearchService.test.ts` | 4 |
| 11 | `test/renderer/hooks/useEditorKeyboardShortcuts.test.ts` | 4 |
| 12 | `test/renderer/components/team/editor/fileIcons.test.ts` | 4 |
| 13 | `test/main/services/editor/GitStatusService.test.ts` | 5 |
| 14 | `test/main/services/editor/EditorFileWatcher.test.ts` | 5 |
| 15 | `test/main/services/editor/conflictDetection.test.ts` | 5 |