в config.
+// reconfigure() на одном View НЕ влияет на cached states в пуле.
+// EDGE CASE: при unmount+remount компонента — cached states ссылаются на старые Compartments.
+// Решение: при remount создать новые Compartments, заново создать EditorState для АКТИВНОГО таба.
+// Evicted LRU states: теряют undo history (ожидаемо), cursor через EditorSelection.
+
+function buildEditorExtensions(options: EditorExtensionOptions): Extension[] {
+ return [
+ // Языковые
+ getLanguageExtension(options.fileName), // внутри тоже Compartment (из codemirrorLanguages.ts)
+ syntaxHighlighting(oneDarkHighlightStyle),
+
+ // UI
+ lineNumbers(),
+ highlightActiveLine(),
+ highlightActiveLineGutter(),
+ bracketMatching(),
+ closeBrackets(),
+
+ // История
+ history(),
+
+ // Поиск (CM6 built-in, @codemirror/search)
+ search(),
+
+ // Настройки через Compartment (переключаются через view.dispatch без потери undo)
+ // ВАЖНО: readOnly требует ОБА facet для корректного UX (паттерн из CodeMirrorDiffView.tsx:482-483):
+ // - EditorState.readOnly — блокирует мутации документа
+ // - EditorView.editable — убирает contenteditable + cursor (без него курсор мигает в read-only)
+ readOnlyCompartment.current.of(options.readOnly
+ ? [EditorView.editable.of(false), EditorState.readOnly.of(true)]
+ : []),
+ lineWrappingCompartment.current.of(options.lineWrapping ? EditorView.lineWrapping : []),
+ tabSizeCompartment.current.of(indentUnit.of(' '.repeat(options.tabSize ?? 2))),
+
+ // Все keymaps ОБЯЗАТЕЛЬНО через keymap.of() — bare KeyBinding[] не является Extension!
+ // Паттерн из CodeMirrorDiffView.tsx:492 и MembersJsonEditor.tsx:40
+ keymap.of([
+ ...defaultKeymap,
+ ...historyKeymap,
+ ...searchKeymap,
+ ...closeBracketsKeymap,
+ indentWithTab,
+ { key: 'Mod-s', run: () => { options.onSave?.(); return true; } },
+ ]),
+
+ // onChange (debounced)
+ EditorView.updateListener.of(update => {
+ if (update.docChanged) options.onContentChanged?.();
+ }),
+
+ // Тема
+ baseEditorTheme, // из codemirrorTheme.ts
+ ];
+}
+
+// Toggle line wrapping (итерация 5) — без потери undo/scroll:
+// view.dispatch({ effects: lineWrappingCompartment.reconfigure(EditorView.lineWrapping) });
+// view.dispatch({ effects: lineWrappingCompartment.reconfigure([]) });
+// Refs на compartments хранить в useRef компонента CodeMirrorEditor
+```
+
+### Определение языка
+
+Функция `getSyncLanguageExtension(fileName)` извлекается из `CodeMirrorDiffView.tsx` в `src/renderer/utils/codemirrorLanguages.ts`. 16+ языков синхронно + `@codemirror/language-data` async fallback для остальных. Используется `Compartment` для ленивой инжекции.
+
+### Тема
+
+Базовая тема извлекается из `diffTheme` (`CodeMirrorDiffView.tsx` строки 158-198) в `src/renderer/utils/codemirrorTheme.ts`:
+
+```typescript
+export const baseEditorTheme = EditorView.theme({
+ '&': {
+ backgroundColor: 'var(--color-surface)',
+ color: 'var(--color-text)',
+ fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace',
+ fontSize: '13px',
+ },
+ '.cm-gutters': {
+ backgroundColor: 'var(--color-surface)',
+ borderRight: '1px solid var(--color-border)',
+ },
+ '.cm-cursor': { borderLeftColor: 'var(--color-text)' },
+ '.cm-selectionBackground': { backgroundColor: 'rgba(100, 153, 255, 0.2)' },
+ // ... остальные базовые стили
+});
+```
+
+Diff-специфичные стили (`.cm-changedLine`, `.cm-deletedChunk`, `.cm-merge-*`, `.cm-collapsedLines`) выносятся в отдельный `const diffSpecificTheme = EditorView.theme({...})` внутри `CodeMirrorDiffView.tsx`. В `buildExtensions()` diff-view использует `[baseEditorTheme, diffSpecificTheme]`, а editor -- только `[baseEditorTheme]`. Light theme работает автоматически через CSS-переменные.
+
+### EditorView lifecycle
+
+Один EditorView, переключение через EditorState pooling. При tab switch ~3-5ms для 100KB файла. Undo history, cursor, selection сохраняются в EditorState.
+
+---
+
+## Keyboard Shortcuts
+
+| Shortcut | Действие | Итерация | Конфликт |
+|----------|---------|----------|----------|
+| `Cmd+S` | Сохранить активный файл | 2 | — (CM6 keymap) |
+| `Cmd+Shift+S` | Сохранить все | 2 | — |
+| `Cmd+W` | Закрыть активный tab | 3 | `useKeyboardShortcuts.ts:155` |
+| `Cmd+P` | Quick Open (fuzzy search файлов) | 4 | — |
+| `Cmd+F` | Поиск в файле (CM6 search) | 2 | `useKeyboardShortcuts.ts:241` |
+| `Cmd+Shift+F` | Поиск по содержимому файлов | 4 | — |
+| `Cmd+Shift+[` / `Cmd+Shift+]` | Переключение табов влево/вправо | 4 | `useKeyboardShortcuts.ts:177` |
+| `Ctrl+Tab` / `Ctrl+Shift+Tab` | Переключение табов (MRU) | 4 | `useKeyboardShortcuts.ts:81` |
+| `Cmd+B` | Toggle file tree sidebar | 4 | `useKeyboardShortcuts.ts:271` |
+| `Cmd+G` | Go to line (CM6 gotoLine) | 4 | — |
+| `Cmd+Z` / `Cmd+Shift+Z` | Undo/Redo (CM6 native) | 2 | — |
+| `Escape` | Закрыть overlay (с confirm при unsaved) | 1 | — |
+
+### Scope Isolation (R1 — решение)
+
+6 из 12 шорткатов конфликтуют с глобальными в `useKeyboardShortcuts.ts`. Решение:
+
+**Approach A: Guard в глобальном handler** (надёжность 8/10)
+
+```typescript
+// useKeyboardShortcuts.ts — добавить guard
+const editorOpen = useStore(s => s.editorProjectPath !== null);
+
+// В handler (bubble phase, window.addEventListener('keydown')):
+if (editorOpen) {
+ // Early return для конфликтующих shortcuts:
+ // Cmd+W, Cmd+B, Cmd+F, Cmd+Shift+[/], Ctrl+Tab
+ const isEditorConflict = (e.metaKey && ['w','b','f'].includes(e.key))
+ || (e.metaKey && e.shiftKey && ['[',']'].includes(e.key))
+ || (e.ctrlKey && e.key === 'Tab');
+ if (isEditorConflict) return;
+}
+```
+
+**Safety net: `stopPropagation` в CM6** — все editor keybindings с `stopPropagation: true`:
+
+```typescript
+keymap.of([
+ { key: 'Mod-f', run: openSearchPanel, stopPropagation: true },
+ { key: 'Mod-s', run: () => { onSave?.(); return true; }, stopPropagation: true },
+ // ...
+]);
+```
+
+**Паттерн подтверждён**: `ChangeReviewDialog` уже использует capture-phase handler с guard (строки 379-408).
+
+Замечания:
+- `Cmd+[` / `Cmd+]` НЕ используются для табов -- это indent/outdent в CM6 и VS Code
+- `Cmd+S` перехватывается через CodeMirror keymap (не глобальный listener) -- нет конфликта с другими горячими клавишами
+- Sidebar width persist в localStorage
+
+---
+
+## CSS-переменные
+
+### Уже имеющиеся (100% достаточно для MVP)
+
+- Surfaces: `--color-surface`, `--color-surface-raised`, `--color-surface-sidebar`
+- Borders: `--color-border`, `--color-border-subtle`, `--color-border-emphasis`
+- Text: `--color-text`, `--color-text-secondary`, `--color-text-muted`
+- Code: `--code-bg`, `--code-border`, `--code-line-number`, `--code-filename`
+- Syntax: `--syntax-string`, `--syntax-comment`, `--syntax-keyword` и все остальные
+- Scrollbar: `--scrollbar-thumb`, `--scrollbar-thumb-hover`
+- Cards: `--card-bg`, `--card-border`, `--card-header-bg`
+
+### Рекомендуемые дополнения (добавить в `:root` в `index.css`)
+
+```css
+--editor-tab-active-bg: var(--color-surface);
+--editor-tab-inactive-bg: var(--color-surface-sidebar);
+--editor-tab-modified-dot: #f59e0b;
+--editor-tab-border: var(--color-border);
+--editor-statusbar-bg: var(--color-surface-sidebar);
+--editor-statusbar-text: var(--color-text-muted);
+--editor-sidebar-resize-handle: rgba(148, 163, 184, 0.15);
+--editor-sidebar-resize-handle-hover: rgba(148, 163, 184, 0.3);
+```
diff --git a/docs/iterations/edit-project/file-list.md b/docs/iterations/edit-project/file-list.md
new file mode 100644
index 00000000..9db766db
--- /dev/null
+++ b/docs/iterations/edit-project/file-list.md
@@ -0,0 +1,176 @@
+# Риски, бенчмарки, полный список файлов
+
+## Риски
+
+| # | Риск | Вероятность | Импакт | Итерация | Митигация |
+|---|------|------------|--------|----------|-----------|
+| 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 | Draft autosave в localStorage (30 сек debounce, max 10 drafts x 500KB). Recovery banner при reopen |
+| 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 применён) |
+
+---
+
+## Тест-стратегия
+
+### Unit-тесты (Vitest)
+~15 файлов, покрывают: сервисы (ProjectFileService, FileSearchService, GitStatusService), store slices, утилиты (fileTreeBuilder, tabLabelDisambiguation, codemirrorLanguages, atomicWrite), IPC wrapper. Запуск: `pnpm test`.
+
+### Integration-тесты (Vitest + happy-dom)
+Для компонентов использующих CM6 — happy-dom НЕ поддерживает `contenteditable` полностью. Стратегия:
+- **CodeMirrorEditor**: тестировать через mock EditorView. Проверять lifecycle (mount → register bridge, unmount → unregister), tab switch (stateCache save/restore), dirty flag propagation
+- **editorSlice + editorBridge**: интеграционный тест — store action вызывает bridge, bridge возвращает mock content
+- **IPC handlers (editor.ts)**: тестировать с mock fs + mock ProjectFileService. Проверять security guards (path traversal, .git/ write block, device paths)
+
+### Manual smoke-тесты (каждая итерация)
+Обязательный чеклист перед мёрджем каждого PR:
+- [ ] Открыть editor, навигировать по дереву, открыть файл — подсветка работает
+- [ ] Редактировать файл, Cmd+S — сохранение без ошибок
+- [ ] Unsaved changes при закрытии — confirmation dialog
+- [ ] ChangeReviewDialog по-прежнему работает корректно (regression)
+- [ ] Горячие клавиши НЕ конфликтуют с глобальными при закрытом editor
+
+### Benchmarks (manual, один раз после iter-4)
+
+Запускать вручную через DevTools Performance tab + React DevTools Profiler. Результаты фиксировать в PR description.
+
+```
+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 раз при наборе
+```
+
+---
+
+## Стратегия отката
+
+Каждая итерация — отдельный PR. При проблемах — revert PR целиком.
+
+| Итерация | Fallback при провале | Минимально жизнеспособный результат |
+|----------|---------------------|-------------------------------------|
+| PR 0 | Revert PR. Рефакторинги механические (извлечение функций), не трогают review logic. При проблеме — revert + дублировать код в editor | — |
+| Iter 1 | Read-only browser без CM6 — просто дерево + `` с raw text | Кнопка "Open in Editor" → файловый браузер |
+| Iter 2 | Оставить read-only из iter-1, открывать файлы в external editor (`shell:openPath`) | Read-only + external editor fallback |
+| Iter 3 | Один таб (последний открытый файл). CRUD через terminal/external | Single-tab editor |
+| Iter 4 | Без Quick Open, без search — ручная навигация по дереву. Без виртуализации (работает до ~2000 файлов) | Editor без search/shortcuts |
+| Iter 5 | Без git badges, без file watcher — ручной F5 refresh. Без conflict detection — last-write-wins | Полный editor без live features |
+
+**Критическая точка невозврата**: нет. Каждый PR изолирован. Даже если iter-5 провалится, iter-1-4 дают полноценный editor без git/watcher.
+
+---
+
+## Полный список файлов
+
+### Новые файлы (~36)
+
+| # | Файл | Итерация | Описание |
+|---|------|----------|----------|
+| 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 через simple-git (~80-100 LOC) |
+| 6 | `src/main/services/editor/EditorFileWatcher.ts` | 5 | FileWatcher через chokidar v4 (~50-70 LOC) |
+| 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 (~250-350 LOC: pooling + LRU + bridge + dirty + autosave) |
+| 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/EditorErrorBoundary.tsx` | 1 | React ErrorBoundary для CM6 (аналог DiffErrorBoundary) |
+| 29 | `src/renderer/components/team/editor/EditorContextMenu.tsx` | 3 | Context menu |
+| 30 | `src/renderer/components/team/editor/NewFileDialog.tsx` | 3 | Inline-input |
+| 31 | `src/renderer/components/team/editor/QuickOpenDialog.tsx` | 4 | Cmd+P dialog |
+| 32 | `src/renderer/components/team/editor/SearchInFilesPanel.tsx` | 4 | Cmd+Shift+F |
+| 33 | `src/renderer/components/team/editor/EditorBreadcrumb.tsx` | 4 | Breadcrumb |
+| 34 | `src/renderer/components/team/editor/EditorShortcutsHelp.tsx` | 4 | Shortcuts modal |
+| 35 | `src/renderer/components/team/editor/fileIcons.ts` | 4 | Иконки файлов |
+| 36 | `src/renderer/components/team/editor/GitStatusBadge.tsx` | 5 | M/U/A/C(conflict) бейджи |
+| 37 | `src/renderer/utils/editorBridge.ts` | 2 | Module-level singleton: Store ↔ CM6 refs bridge (R3) |
+
+### Модификации существующих файлов (~18)
+
+| # | Файл | Итерация | Изменение |
+|---|------|----------|-----------|
+| 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` | 1 (расш. 5) | `mainWindow.on('closed')` → `cleanupEditorState()` (базовый reset в iter-1, watcher cleanup в iter-5) |
+| 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 |
diff --git a/docs/iterations/edit-project/iter-0-refactoring.md b/docs/iterations/edit-project/iter-0-refactoring.md
new file mode 100644
index 00000000..c2ce1d83
--- /dev/null
+++ b/docs/iterations/edit-project/iter-0-refactoring.md
@@ -0,0 +1,104 @@
+# PR 0: Обязательные рефакторинги (R1-R4)
+
+> Перед итерацией 1. Отдельный PR.
+
+## Цель
+
+Обязательные рефакторинги -- без них будет дублирование кода. Выполняются ДО написания нового кода. Тесты `ReviewFileTree` и `CodeMirrorDiffView` должны проходить после рефакторинга (zero behavior change).
+
+## Почему отдельный PR
+
+R1 затрагивает production `ReviewFileTree` (используется в `ChangeReviewDialog`), R3 затрагивает production `CodeMirrorDiffView`. Объединение рефакторинга production-кода + 15 новых файлов в одну итерацию — чрезмерный blast radius (28 файлов). Разделение:
+- **PR 0 ("Refactoring")**: R1-R4 + тесты. Мёрдж только после проверки что ChangeReviewDialog работает корректно.
+- **PR 1 ("Walking Skeleton")**: Новые editor-файлы. Зависит от PR 0.
+
+## Рефакторинги
+
+| # | Что извлечь | Откуда | Куда | LOC |
+|---|-------------|--------|------|-----|
+| R1 | `buildTree()` + `collapse()` + сортировка | `ReviewFileTree.tsx:42-83` | `src/renderer/utils/fileTreeBuilder.ts` | ~50 |
+| R2 | `getSyncLanguageExtension()` + `getAsyncLanguageDesc()` | `CodeMirrorDiffView.tsx:64-128` | `src/renderer/utils/codemirrorLanguages.ts` | ~70 |
+| R3 | Базовая тема CM (без diff-стилей) | `CodeMirrorDiffView.tsx:158-198` | `src/renderer/utils/codemirrorTheme.ts` | ~40 |
+| R4 | `wrapReviewHandler()` | `review.ts:133-145` | `src/main/ipc/ipcWrapper.ts` | ~15 |
+
+## Детали каждого рефакторинга
+
+### R1: `buildTree()` — Generic tree builder
+
+**NB**: `ReviewFileTree` работает с `FileChangeSummary` (имеет `status`, `additions`, `deletions`), а editor использует `FileTreeEntry` (имеет `size`, `children`). `buildTree()` должен быть generic по типу node, принимая `getPath: (item: T) => string` и `isDirectory: (item: T) => boolean` как параметры.
+
+```typescript
+// src/renderer/utils/fileTreeBuilder.ts
+function buildTree(
+ items: T[],
+ getPath: (item: T) => string,
+ isDirectory: (item: T) => boolean
+): TreeNode[]
+```
+
+### R2: `getSyncLanguageExtension()` — Языковой маппинг
+
+Извлечь из `CodeMirrorDiffView.tsx:64-128`. 16+ языков синхронно + `@codemirror/language-data` async fallback.
+
+### R3: `baseEditorTheme` — Базовая тема
+
+**NB**: `diffTheme` — один `EditorView.theme({...})` на 125 строк. Рефакторинг:
+1. Извлечь строки 158-198 в `baseEditorTheme = EditorView.theme({...})` в `codemirrorTheme.ts`
+2. В `CodeMirrorDiffView.tsx` создать `const diffSpecificTheme = EditorView.theme({...})` со строками 199-283
+3. В `buildExtensions()` заменить `diffTheme` на `[baseEditorTheme, diffSpecificTheme]`
+
+### R4: `createIpcWrapper()` — Общий IPC wrapper
+
+**NB**: `teams.ts` имеет аналогичный `wrapTeamHandler` (40+ вызовов), но его миграция — отдельный follow-up PR после итерации 1. Blast radius слишком высокий (1755 строк) для совмещения с основной фичей. В итерации 1 R4 применяется ТОЛЬКО к `review.ts` + новому `editor.ts`.
+
+```typescript
+// src/main/ipc/ipcWrapper.ts
+export function createIpcWrapper(logPrefix: string) {
+ const log = createLogger(logPrefix);
+ return async function wrap(op: string, fn: () => Promise): Promise> {
+ try { return { success: true, data: await fn() }; }
+ catch (error) {
+ const msg = error instanceof Error ? error.message : String(error);
+ log.error(`handler error [${op}]:`, msg);
+ return { success: false, error: msg };
+ }
+ };
+}
+
+// review.ts:
+const wrapHandler = createIpcWrapper('IPC:review');
+
+// editor.ts:
+const wrapHandler = createIpcWrapper('IPC:editor');
+```
+
+## После рефакторинга
+
+- `ReviewFileTree.tsx` импортирует `buildTree`, `TreeNode` из `fileTreeBuilder.ts`
+- `CodeMirrorDiffView.tsx` импортирует из `codemirrorLanguages.ts` и `codemirrorTheme.ts`
+- `review.ts` импортирует `createIpcWrapper` из `ipcWrapper.ts`
+- `teams.ts` — миграция `wrapTeamHandler` → `createIpcWrapper` в отдельном follow-up PR (40+ замен, высокий blast radius)
+
+## Уровень риска по рефакторингам
+
+| R# | Что трогает | Уровень риска | Почему |
+|----|-------------|--------------|--------|
+| R1 | ReviewFileTree (дерево файлов) | Низкий | Извлечение чистой функции buildTree(). Diff-viewer, hunks, approve/reject — не затрагиваются |
+| R2 | CodeMirrorDiffView (языки) | Около нуля | Чистые stateless функции, просто выносим в отдельный файл |
+| R3 | CodeMirrorDiffView (тема) | Низкий | Разрезка CSS-объекта на base + diff-specific. Визуально проверить |
+| R4 | review.ts (IPC wrapper) | Около нуля | 15 LOC try-catch factory, тривиальная замена |
+
+**Что НЕ затрагивается**: ChangeReviewDialog logic, diff rendering, hunks, approve/reject, комментарии, changeReviewSlice state.
+
+## Критерии готовности
+
+- [ ] `pnpm typecheck` проходит
+- [ ] `pnpm test` проходит (zero behavior change)
+- [ ] Новые unit-тесты для `fileTreeBuilder.ts` и `ipcWrapper.ts`
+- [ ] Новые unit-тесты для `codemirrorLanguages.ts` — проверка маппинга расширений на языки
+- [ ] Manual smoke-тест: открыть ChangeReviewDialog → файловое дерево рендерится корректно, diff подсвечивается
+
+## Оценка
+
+- **Надёжность решения: 9/10** — все 4 рефакторинга механические (извлечение функций без изменения поведения). R2/R4 — около нуля риска, R1/R3 — низкий.
+- **Уверенность: 9/10** — код review workflow не затрагивается, трогается только утилитарная часть (дерево файлов, языки, тема, wrapper).
diff --git a/docs/iterations/edit-project/iter-1-walking-skeleton.md b/docs/iterations/edit-project/iter-1-walking-skeleton.md
new file mode 100644
index 00000000..bfdf6b7f
--- /dev/null
+++ b/docs/iterations/edit-project/iter-1-walking-skeleton.md
@@ -0,0 +1,116 @@
+# Итерация 1: Walking Skeleton (read-only файловый браузер)
+
+> Зависит от: [PR 0 (Рефакторинги)](iter-0-refactoring.md)
+
+## Цель
+
+Минимальный end-to-end вертикальный срез -- кнопка "Open in Editor" на TeamDetailView открывает полноэкранный overlay с деревом файлов слева и содержимым файла с подсветкой синтаксиса (read-only) справа.
+
+## Новые npm-зависимости
+
+- `@codemirror/search` (`pnpm add @codemirror/search`) — встроенный Cmd+F поиск в файле
+- `isbinaryfile` v5.0.7 (`pnpm add isbinaryfile`) — binary detection (33M downloads/нед, zero deps, умнее null-byte scan: UTF-16, BOM, encoding hints)
+
+## IPC каналы
+
+| Канал | Описание |
+|-------|----------|
+| `editor:open` | Инициализировать editor, установить activeProjectRoot в module-level state |
+| `editor:close` | Cleanup: сброс activeProjectRoot, остановка watcher |
+| `editor:readDir` | Рекурсивное чтение директории (depth=1, lazy) |
+| `editor:readFile` | Чтение содержимого файла с binary detection |
+
+## Новые файлы
+
+| # | Файл | Описание |
+|---|------|----------|
+| 1 | `src/shared/types/editor.ts` | `FileTreeEntry`, `ReadDirResult`, `ReadFileResult` |
+| 2 | `src/main/services/editor/ProjectFileService.ts` | Stateless сервис: `readDir`, `readFile` с полной валидацией |
+| 3 | `src/main/services/editor/index.ts` | Barrel export: `{ ProjectFileService }` (расширяется в итерациях 4-5) |
+| 4 | `src/main/ipc/editor.ts` | IPC handlers с module-level `activeProjectRoot` |
+| 5 | `src/main/ipc/ipcWrapper.ts` | Общий `createIpcWrapper()` (рефакторинг из review.ts) |
+| 6 | `src/renderer/store/slices/editorSlice.ts` | Минимальный slice: Группа 1 (tree state + actions) |
+| 7 | `src/renderer/utils/fileTreeBuilder.ts` | Generic `buildTree()` (рефакторинг из ReviewFileTree) |
+| 8 | `src/renderer/utils/codemirrorLanguages.ts` | `getSyncLanguageExtension()` (рефакторинг) |
+| 9 | `src/renderer/utils/codemirrorTheme.ts` | `baseEditorTheme` (рефакторинг) |
+| 10 | `src/renderer/components/common/FileTree.tsx` | Generic FileTree с render-props |
+| 11 | `src/renderer/components/team/editor/ProjectEditorOverlay.tsx` | Full-screen overlay |
+| 12 | `src/renderer/components/team/editor/EditorFileTree.tsx` | Обёртка над generic FileTree |
+| 13 | `src/renderer/components/team/editor/CodeMirrorEditor.tsx` | Read-only CM6 view (один EditorView, без pooling пока) |
+| 14 | `src/renderer/components/team/editor/EditorEmptyState.tsx` | Нет открытых файлов |
+| 15 | `src/renderer/components/team/editor/EditorBinaryState.tsx` | Заглушка для бинарных файлов |
+| 16 | `src/renderer/components/team/editor/EditorErrorState.tsx` | Заглушка для ошибок чтения |
+| 17 | `src/renderer/components/team/editor/EditorErrorBoundary.tsx` | React ErrorBoundary для CM6 (аналог DiffErrorBoundary) |
+
+## Изменения в существующих файлах
+
+| # | Файл | Изменение |
+|---|------|-----------|
+| 1 | `src/shared/types/api.ts` | `EditorAPI` interface + `editor: EditorAPI` в `ElectronAPI` |
+| 2 | `src/shared/types/index.ts` | +`export type * from './editor'` (barrel re-export, паттерн как team/review/terminal) |
+| 3 | `src/preload/constants/ipcChannels.ts` | `EDITOR_OPEN`, `EDITOR_CLOSE`, `EDITOR_READ_DIR`, `EDITOR_READ_FILE` |
+| 4 | `src/preload/index.ts` | Секция `editor: { ... }` в `electronAPI` |
+| 5 | `src/main/ipc/handlers.ts` | `initializeEditorHandlers` + `registerEditorHandlers` |
+| 6 | `src/main/ipc/review.ts` | Заменить `wrapReviewHandler` на import из `ipcWrapper.ts` |
+| 7 | `src/renderer/components/team/TeamDetailView.tsx` | Кнопка "Open in Editor" + state для overlay |
+| 8 | `src/renderer/components/team/review/ReviewFileTree.tsx` | Рефакторинг: использовать generic FileTree + fileTreeBuilder |
+| 9 | `src/renderer/components/team/review/CodeMirrorDiffView.tsx` | Рефакторинг: импорт из codemirrorLanguages/Theme |
+| 10 | `src/main/utils/pathValidation.ts` | Добавить `validateFileName()`, `isDevicePath()`, `isGitInternalPath()`. Экспортировать `matchesSensitivePattern()` (приватная) для `isSensitive` в readDir. Экспортировать `isPathWithinRoot()` (приватная, строка ~30) — нужна для SEC-15 в `editor:open` handler уже в iter-1, а также для SEC-14 write-handler guard в iter-2 |
+| 11 | `src/main/index.ts` | Добавить базовый cleanup в `mainWindow.on('closed')`: вызвать `cleanupEditorState()` (экспорт из editor.ts, сбрасывает `activeProjectRoot = null`). Без этого при Cmd+Q на macOS state "утечёт" и `editor:open` откажет при следующем открытии окна. Полный watcher cleanup — итерация 5, но базовый reset нужен с итерации 1 |
+| 12 | `src/renderer/api/httpClient.ts` | Stub для `editor: EditorAPI` — throw "Editor is not available in browser mode" (паттерн как `review`, `terminal`, `teams`) |
+| 13 | `src/renderer/store/types.ts` | `EditorSlice` в AppState |
+| 14 | `src/renderer/store/index.ts` | `createEditorSlice` |
+
+## Security-требования
+
+1. **SEC-15**: `editor:open` handler валидирует `projectPath` ДО установки `activeProjectRoot`: `path.isAbsolute()`, `fs.stat().isDirectory()`, `!== '/'`/`'C:\\'`, `!isPathWithinRoot(path, claudeDir)`. Без этого злонамеренный renderer может передать `"/"`, делая ВСЕ пути валидными
+2. `ProjectFileService.readDir()`: для каждого entry проверять containment через `isPathWithinAllowedDirectories()` (экспортирована из pathValidation.ts). Для symlinks -- `fs.realpath()` + повторная проверка containment. Молча пропускать entries за пределами projectRoot (SEC-2). **НЕ вызывать `validateFilePath()` целиком** — она блокирует sensitive файлы, а readDir должен их ПОКАЗЫВАТЬ с пометкой `isSensitive: true`. Для пометки использовать новую экспортируемую функцию `matchesSensitivePattern()` из pathValidation.ts (сейчас приватная — нужно экспортировать) (SEC-6)
+3. `ProjectFileService.readFile()`: `fs.lstat()` -> `isFile()` ДО чтения. `stats.size <= 2MB`. Block device paths. Post-read realpath verify (SEC-3, SEC-4)
+4. `activeProjectRoot` в module-level state, НЕ от renderer (SEC-5)
+5. Sensitive файлы: показывать с замком в дереве, "Sensitive file, cannot open" при клике (SEC-6)
+
+## Performance-требования
+
+- MAX_ENTRIES_PER_DIR = 500; при превышении -- "N more files..."
+- readFile тиерная стратегия: <256KB мгновенно, 256KB-2MB progress, 2MB-5MB preview, >5MB external
+- Binary detection: `isbinaryfile` (v5.0.7) — `isBinaryFile(filePath)` вместо ручного null-byte scan
+- Дедупликация IPC: `Map>` для readFile
+
+## UX-требования
+
+- Focus management: при открытии -- фокус на первый файл. При закрытии -- вернуть фокус на кнопку. `inert` на фон
+- ARIA: file tree сразу с `role="tree"`, `role="treeitem"`, `aria-expanded`, `role="group"`
+- Пустой проект: "No files found" + кнопка Create (неактивна до итерации 3)
+- Binary файлы: `EditorBinaryState.tsx` с кнопкой "Open in System Viewer"
+- Max indent 12 уровней, tooltip на глубоких узлах
+
+## State management
+
+Создать минимальный `editorSlice` уже на итерации 1 с полями `editorProjectPath`, `editorFileTree`, `editorFileTreeLoading`, `editorFileTreeError`, `openEditor()`, `closeEditor()`, `loadFileTree()`, `expandDirectory()`. Это избавит от болезненной миграции useState → Zustand на итерации 2. Табы и dirty-состояние добавляются в slice на итерации 2.
+
+## Тестирование
+
+| # | Что тестировать | Файл |
+|---|----------------|------|
+| 1 | `ProjectFileService` -- чтение директории с mock fs, проверка security (reject paths outside projectRoot), исключение node_modules, symlink escape | `test/main/services/editor/ProjectFileService.test.ts` |
+| 2 | `editorSlice` -- open/close editor, loadFileTree, expandDirectory | `test/renderer/store/editorSlice.test.ts` |
+| 3 | `EditorFileTree` -- snapshot тесты рендеринга | — |
+| 4 | `fileTreeBuilder.ts` -- unit тесты `buildTree()` (с generic типами для FileChangeSummary и FileTreeEntry) | `test/renderer/utils/fileTreeBuilder.test.ts` |
+| 5 | `ipcWrapper.ts` -- unit тесты createIpcWrapper | `test/main/ipc/ipcWrapper.test.ts` |
+| 6 | Manual: открыть TeamDetailView -> "Open in Editor" -> дерево загружается -> клик по файлу -> подсветка синтаксиса | — |
+
+## Критерии готовности
+
+- [ ] Кнопка видна на TeamDetailView рядом с путём проекта
+- [ ] Overlay открывается по клику, закрывается по Escape или X
+- [ ] Дерево файлов загружается для projectPath команды
+- [ ] Клик по файлу показывает содержимое с синтаксической подсветкой
+- [ ] Binary файлы показывают заглушку
+- [ ] Попытка прочитать файл за пределами проекта -- отказ
+- [ ] `pnpm typecheck` проходит
+- [ ] Рефакторинги R1-R4 выполнены, тесты ReviewFileTree и CodeMirrorDiffView проходят
+
+## Оценка
+
+- **Надёжность решения: 8/10** -- CodeMirror 6 проверен в продакшене, все зависимости в проекте, паттерны повторяют ChangeReviewDialog.
+- **Уверенность: 9/10** -- самый понятный этап, минимум неизвестных.
diff --git a/docs/iterations/edit-project/iter-2-editable-save.md b/docs/iterations/edit-project/iter-2-editable-save.md
new file mode 100644
index 00000000..f26b38dc
--- /dev/null
+++ b/docs/iterations/edit-project/iter-2-editable-save.md
@@ -0,0 +1,107 @@
+# Итерация 2: Editable CodeMirror + сохранение файлов
+
+> Зависит от: [Итерация 1](iter-1-walking-skeleton.md)
+
+## Цель
+
+Переключить CodeMirror из read-only в редактируемый режим. Cmd+S для сохранения. Индикатор unsaved changes. Status bar.
+
+## IPC каналы
+
+| Канал | Описание |
+|-------|----------|
+| `editor:writeFile` | Запись файла (atomic write через tmp + rename) |
+
+## Новые файлы
+
+| # | Файл | Описание |
+|---|------|----------|
+| 1 | `src/main/utils/atomicWrite.ts` | Перемещение существующего `atomicWriteAsync()` из `src/main/services/team/atomicWrite.ts` (shared utility). **H2**: Blast radius — ~10 source файлов + ~4 тестовых файла (TeamTaskWriter, TeamDataService, TeamKanbanManager, TeamAgentToolsInstaller, и их тесты). Обновить все импорты |
+| 2 | `src/renderer/components/team/editor/EditorTabBar.tsx` | Панель вкладок (один файл пока, подготовка к multi-tab) |
+| 3 | `src/renderer/components/team/editor/EditorStatusBar.tsx` | Ln:Col, язык, отступы |
+| 4 | `src/renderer/components/team/editor/EditorToolbar.tsx` | Save, Undo, Redo |
+| 5 | `src/renderer/utils/editorBridge.ts` | Module-level singleton: Store ↔ CM6 refs bridge (R3). Компонент вызывает `register()` при mount, store actions используют `getContent()`/`destroy()` |
+
+## Изменения в существующих файлах
+
+| # | Файл | Изменение |
+|---|------|-----------|
+| 1 | `src/shared/types/editor.ts` | Типы для write request/response |
+| 2 | `src/shared/types/api.ts` | `writeFile` в `EditorAPI` |
+| 3 | `src/main/services/editor/ProjectFileService.ts` | Метод `writeFile()` с atomic write |
+| 4 | `src/main/ipc/editor.ts` | Handler `editor:writeFile` |
+| 5 | `src/preload/index.ts` | `editor.writeFile` |
+| 6 | `src/preload/constants/ipcChannels.ts` | `EDITOR_WRITE_FILE` |
+| 7 | `src/renderer/components/team/editor/ProjectEditorOverlay.tsx` | Интеграция TabBar, StatusBar |
+| 8 | `src/renderer/components/team/editor/CodeMirrorEditor.tsx` | Убрать readOnly, EditorState pooling (Map), Cmd+S keymap |
+| 9 | `src/renderer/store/slices/editorSlice.ts` | Расширить: +Группа 2 (tabs) + Группа 3 (dirty/save) |
+| 10 | `src/renderer/index.css` | +8 editor CSS-переменных (--editor-tab-active-bg, --editor-tab-modified-dot и др.) |
+
+## Security-требования
+
+1. `writeFile`: `validateFilePath()` ДО записи. **+ SEC-14**: `isPathWithinRoot(normalizedPath, activeProjectRoot)` для блокировки `~/.claude` writes. `Buffer.byteLength(content, 'utf8') <= 2MB`. Atomic write. Запрет записи в `.git/`. `activeProjectRoot` из module-level state (SEC-9, SEC-12)
+2. Файл удалён извне при save: ENOENT -> inline-ошибка "File was deleted. Create new? / Close tab" (не падать)
+
+## Performance-требования
+
+- НЕ хранить modified content в Zustand. Контент только в EditorState CM. В store: `editorModifiedFiles: Record` (dirty flags — Record вместо Set, т.к. Zustand не отслеживает мутации Set)
+- Dirty flag через debounced `EditorView.updateListener` (300ms)
+- Гранулярные Zustand-селекторы: FileTreePanel не подписывается на tabs/content
+- EditorState pooling: один EditorView, Map в useRef
+- LRU eviction при > 30 states. При eviction: удалить dirty flag из `editorModifiedFiles` (предотвращает stale dirty indicator), очистить draft из localStorage
+
+## Autosave (draft recovery)
+
+Минимальный autosave для защиты от потери данных при crash/kill:
+
+```typescript
+// В CodeMirrorEditor.tsx — debounced 30 секунд после последнего изменения
+const AUTOSAVE_DELAY = 30_000;
+
+// Сохранять draft в localStorage:
+// key: `editor-draft:${filePath}`
+// value: JSON.stringify({ content: doc.toString(), timestamp: Date.now() })
+// Очищать при успешном saveFile() или при closeTab()
+
+// При openFile() — проверить наличие draft:
+// if (draft exists && draft.timestamp > file.mtimeMs) → banner "Recovered unsaved changes. [Apply] [Discard]"
+// Edge case: если draft.timestamp < file.mtimeMs — файл был изменён извне ПОСЛЕ draft → draft устарел, удалить молча
+// Edge case: если file.mtimeMs === 0 или undefined (новый файл) — применять draft безусловно
+```
+
+Лимиты: max 10 drafts, max 500KB per draft. При превышении — вытеснять oldest. Не сохранять draft для read-only/preview файлов.
+
+## UX-требования
+
+- Status bar: `[Ln 42, Col 15] | [TypeScript] | [UTF-8] | [Spaces: 2]`
+- Unsaved changes при закрытии overlay: три кнопки ("Save All & Close" / "Discard & Close" / "Cancel")
+- Dirty indicator (точка) на вкладке ПЕРЕД текстом
+- `hasUnsavedChanges()` в slice
+- `closeTab()` с dirty state: показать confirm dialog ("Save / Discard / Cancel") перед закрытием. При "Save" — сохранить через IPC, при "Discard" — закрыть + удалить draft, при "Cancel" — отмена. Закрытие overlay с dirty tabs — аналогично через "Save All & Close" / "Discard & Close" / "Cancel"
+- Draft recovery banner при обнаружении несохранённого draft
+
+## Тестирование
+
+| # | Что тестировать | Файл |
+|---|----------------|------|
+| 1 | `ProjectFileService.writeFile` -- запись с mock fs, reject для файлов вне проекта, atomic write | `test/main/services/editor/ProjectFileService.test.ts` (расширение) |
+| 2 | `editorSlice` -- open/close файлы, dirty state, save | `test/renderer/store/editorSlice.test.ts` (расширение) |
+| 3 | `atomicWrite` -- unit тесты | `test/main/utils/atomicWrite.test.ts` |
+| 4 | EditorState pooling -- save/restore state при switch tab | — |
+| 5 | Draft autosave — сохранение в localStorage, recovery при reopen, cleanup при save/close | — (manual + unit для localStorage logic) |
+| 6 | Manual: открыть файл -> отредактировать -> Cmd+S -> dirty indicator сбрасывается | — |
+
+## Критерии готовности
+
+- [ ] Файл редактируется в CodeMirror (не read-only)
+- [ ] Cmd+S сохраняет файл через atomic write
+- [ ] Dirty indicator на вкладке
+- [ ] Status bar показывает позицию курсора и язык
+- [ ] При закрытии overlay с unsaved changes -- confirmation dialog
+- [ ] Draft autosave: после 30 сек без сохранения — draft в localStorage, recovery при reopen
+- [ ] Benchmark: 0 re-render FileTreePanel/TabBar при наборе текста
+
+## Оценка
+
+- **Надёжность решения: 7/10** -- atomic write и EditorState pooling добавляют сложность.
+- **Уверенность: 8/10** -- паттерны известны, но dirty tracking через CM6 updateListener требует тестирования.
diff --git a/docs/iterations/edit-project/iter-3-multi-tab-crud.md b/docs/iterations/edit-project/iter-3-multi-tab-crud.md
new file mode 100644
index 00000000..46dd4f66
--- /dev/null
+++ b/docs/iterations/edit-project/iter-3-multi-tab-crud.md
@@ -0,0 +1,83 @@
+# Итерация 3: Multi-tab + создание/удаление файлов
+
+> Зависит от: [Итерация 2](iter-2-editable-save.md)
+
+## Цель
+
+Поддержка нескольких открытых файлов во вкладках. Контекстное меню: создать файл/папку, удалить. Tab management.
+
+## Новые npm-зависимости
+
+`@radix-ui/react-context-menu` (`pnpm add @radix-ui/react-context-menu`) — для нативного контекстного меню. Проверить текущие `@radix-ui/*` версии в package.json и использовать совместимую.
+
+## IPC каналы
+
+| Канал | Описание |
+|-------|----------|
+| `editor:createFile` | Создать файл (validateFileName + валидация parentDir) |
+| `editor:createDir` | Создать директорию |
+| `editor:deleteFile` | Удалить файл через `shell.trashItem()` (безопасно) |
+
+## Новые файлы
+
+| # | Файл | Описание |
+|---|------|----------|
+| 1 | `src/renderer/components/team/editor/EditorContextMenu.tsx` | Context menu (New File, New Folder, Delete, Reveal in Finder) |
+| 2 | `src/renderer/components/team/editor/NewFileDialog.tsx` | Inline-input для имени файла/папки |
+| 3 | `src/renderer/utils/tabLabelDisambiguation.ts` | `getDisambiguatedTabLabel()` для дублей "index.ts" |
+
+## Изменения в существующих файлах
+
+| # | Файл | Изменение |
+|---|------|-----------|
+| 1 | `src/shared/types/editor.ts` | Типы для create/delete |
+| 2 | `src/shared/types/api.ts` | `createFile`, `createDir`, `deleteFile` в EditorAPI |
+| 3 | `src/main/services/editor/ProjectFileService.ts` | `createFile()`, `createDir()`, `deleteFile()` |
+| 4 | `src/main/ipc/editor.ts` | 3 новых handler |
+| 5 | `src/preload/index.ts` | 3 новых метода |
+| 6 | `src/preload/constants/ipcChannels.ts` | `EDITOR_CREATE_FILE`, `EDITOR_CREATE_DIR`, `EDITOR_DELETE_FILE` |
+| 7 | `src/renderer/components/team/editor/EditorTabBar.tsx` | Multi-tab: массив, переключение, close, middle-click close |
+| 8 | `src/renderer/components/team/editor/EditorFileTree.tsx` | Right-click context menu, refresh после create/delete |
+| 9 | `src/renderer/store/slices/editorSlice.ts` | Tab management actions, file operations |
+
+## Security-требования
+
+1. `createFile`: `validateFileName()` -- запрет `.`, `..`, control chars, path separators, NUL, length > 255. Валидировать и `parentDir`, и `path.join(parentDir, name)` (SEC-7)
+2. `deleteFile`: `shell.trashItem()`, НЕ `fs.unlink()`. `validateFilePath()` обязательна
+3. Confirmation dialog перед удалением
+4. `createFile`, `createDir`, `deleteFile`: `isGitInternalPath()` блокирует операции внутри `.git/` (SEC-12, аналог writeFile из iter-2)
+
+## Performance-требования
+
+- Tab closing: `stateCache.delete(tabId)` (явная очистка памяти). closeAllTabs: `stateCache.clear()`
+- Debounce обновления дерева после create/delete (500ms), не перечитывать после каждой операции
+
+## UX-требования
+
+- Disambiguation tab labels: два "index.ts" -> "(main/utils)" и "(renderer/utils)"
+- Длинные имена: max-width ~160px, `truncate`, tooltip. Modified dot ПЕРЕД текстом
+- ARIA для tab bar: `role="tablist"`, `role="tab"`, `aria-selected`, `role="tabpanel"`
+
+## Тестирование
+
+| # | Что тестировать | Файл |
+|---|----------------|------|
+| 1 | `ProjectFileService.createFile/deleteFile` с mock fs | `test/main/services/editor/ProjectFileService.test.ts` (расширение) |
+| 2 | `editorSlice` -- multi-tab actions (open, close, reorder) | `test/renderer/store/editorSlice.test.ts` (расширение) |
+| 3 | `tabLabelDisambiguation.ts` -- unit тесты | `test/renderer/utils/tabLabelDisambiguation.test.ts` |
+| 4 | `EditorContextMenu` -- рендеринг, клики | `test/renderer/components/team/editor/EditorContextMenu.test.ts` |
+| 5 | Manual: несколько файлов -> вкладки -> создать файл -> удалить файл | — |
+
+## Критерии готовности
+
+- [ ] Несколько файлов открыты одновременно
+- [ ] Вкладки переключаются, закрываются (X, middle-click)
+- [ ] Right-click -> New File, New Folder, Delete
+- [ ] Создание файла добавляет в дерево + автоматически открывает
+- [ ] Удаление через Trash с confirmation
+- [ ] Disambiguation labels для дублирующихся имён
+
+## Оценка
+
+- **Надёжность решения: 7/10** -- file operations с правильной валидацией и trash -- надёжный подход.
+- **Уверенность: 8/10** -- паттерны файловых операций отработаны.
diff --git a/docs/iterations/edit-project/iter-4-search-shortcuts.md b/docs/iterations/edit-project/iter-4-search-shortcuts.md
new file mode 100644
index 00000000..5a8a0fe7
--- /dev/null
+++ b/docs/iterations/edit-project/iter-4-search-shortcuts.md
@@ -0,0 +1,103 @@
+# Итерация 4: Горячие клавиши, поиск, UX polish
+
+> Зависит от: [Итерация 3](iter-3-multi-tab-crud.md)
+
+## Цель
+
+Клавиатурная навигация, Quick Open (Cmd+P), поиск по файлам (Cmd+Shift+F), breadcrumb, иконки файлов, виртуализация дерева.
+
+## IPC каналы
+
+| Канал | Описание |
+|-------|----------|
+| `editor:searchInFiles` | Literal string search, max 100 results, max 1MB/файл |
+
+## Новые файлы
+
+| # | Файл | Описание |
+|---|------|----------|
+| 1 | `src/renderer/components/team/editor/QuickOpenDialog.tsx` | Cmd+P: fuzzy search через `cmdk` |
+| 2 | `src/renderer/components/team/editor/SearchInFilesPanel.tsx` | Cmd+Shift+F: результаты поиска |
+| 3 | `src/renderer/components/team/editor/EditorBreadcrumb.tsx` | Breadcrumb навигация (кликабельный) |
+| 4 | `src/renderer/components/team/editor/EditorShortcutsHelp.tsx` | Модальное окно shortcuts (кнопка ?) |
+| 5 | `src/renderer/components/team/editor/fileIcons.ts` | Маппинг расширений на lucide-react иконки/цвета |
+| 6 | `src/renderer/hooks/useEditorKeyboardShortcuts.ts` | Все горячие клавиши редактора. CM6 keybindings с `stopPropagation: true` |
+| 7 | `src/main/services/editor/FileSearchService.ts` | Search in files (literal, с лимитами) |
+
+## Изменения в существующих файлах
+
+| # | Файл | Изменение |
+|---|------|-----------|
+| 1 | `src/shared/types/editor.ts` | Типы SearchResult |
+| 2 | `src/shared/types/api.ts` | `searchInFiles` в EditorAPI |
+| 3 | `src/main/ipc/editor.ts` | Handler `editor:searchInFiles` |
+| 4 | `src/preload/index.ts` | `editor.searchInFiles` |
+| 5 | `src/preload/constants/ipcChannels.ts` | `EDITOR_SEARCH_IN_FILES` |
+| 6 | `src/renderer/components/team/editor/ProjectEditorOverlay.tsx` | QuickOpen, SearchInFiles, Breadcrumb, shortcuts |
+| 7 | `src/renderer/components/team/editor/EditorFileTree.tsx` | Виртуализация через react-virtual + иконки файлов |
+| 8 | `src/renderer/components/team/editor/EditorTabBar.tsx` | Иконки файлов на вкладках |
+
+## Security-требования
+
+1. `searchInFiles`: ТОЛЬКО literal string search, НЕ regex. Default case-insensitive (`line.toLowerCase().includes(query.toLowerCase())` — ReDoS-безопасно). Опция `caseSensitive?: boolean` в параметрах. Max 1000 файлов, max 1MB/файл. Каждый файл валидируется через `validateFilePath()`. AbortController timeout 5s (SEC-8). **Cancellation**: предыдущий поиск отменяется AbortController при новом запросе (debounce 300ms на renderer перед IPC вызовом)
+
+## Performance-требования
+
+- File tree виртуализация: `@tanstack/react-virtual` -- `flattenTree()` + `useVirtualizer({ estimateSize: () => 28 })`
+- Quick Open: кешировать flat file list при открытии editor. Invalidate по file watcher event или F5
+- Search in files: запускать с AbortController timeout. На renderer: debounce 300ms + отмена предыдущего IPC запроса при новом вводе (хранить `abortControllerRef` в SearchInFilesPanel)
+
+## Keyboard Scope Isolation (R1)
+
+**Обязательный шаг**: добавить guard в `useKeyboardShortcuts.ts` для 6 конфликтующих shortcuts:
+
+```typescript
+// В useKeyboardShortcuts.ts:
+const editorOpen = useStore(s => s.editorProjectPath !== null);
+// В handler — early return для конфликтов при editorOpen === true
+```
+
+Конкретные конфликты: `Cmd+W` (:155), `Cmd+B` (:271), `Cmd+F` (:241), `Cmd+Shift+[/]` (:177), `Ctrl+Tab` (:81).
+
+Плюс в `useEditorKeyboardShortcuts.ts` — все CM6 keybindings с `stopPropagation: true` как safety net.
+
+**Keyboard scope для диалогов внутри editor**: Escape в QuickOpenDialog/SearchInFilesPanel закрывает ДИАЛОГ, не overlay. Реализация: диалоги вызывают `e.stopPropagation()` на Escape, overlay слушает Escape только когда нет открытых диалогов (state-guard `quickOpenVisible || searchPanelVisible`).
+
+## Изменения в существующих файлах (доп.)
+
+| # | Файл | Изменение |
+|---|------|-----------|
+| 9 | `src/renderer/hooks/useKeyboardShortcuts.ts` | Guard `editorOpen` → early return для 6 конфликтующих shortcuts (R1) |
+
+## UX-требования
+
+- `Cmd+Shift+[`/`]` для табов (НЕ `Cmd+[/]` -- это indent/outdent!)
+- `Cmd+B` toggle sidebar, width persist в localStorage
+- `Cmd+G` go to line (CM6 gotoLine)
+- EmptyState показывает шпаргалку shortcuts
+- Кнопка `?` в header overlay
+- Breadcrumb: каждый сегмент кликабелен -- открывает папку в дереве
+
+## Тестирование
+
+| # | Что тестировать | Файл |
+|---|----------------|------|
+| 1 | `FileSearchService` -- поиск по mock файлам, лимиты | `test/main/services/editor/FileSearchService.test.ts` |
+| 2 | `useEditorKeyboardShortcuts` -- обработка горячих клавиш | `test/renderer/hooks/useEditorKeyboardShortcuts.test.ts` |
+| 3 | `fileIcons.ts` -- маппинг расширений | `test/renderer/components/team/editor/fileIcons.test.ts` |
+| 4 | Виртуализация: benchmark 5000+ файлов, FPS >= 55fps | — |
+| 5 | Manual: Cmd+P, Cmd+Shift+F, навигация клавиатурой | — |
+
+## Критерии готовности
+
+- [ ] Cmd+P открывает quick open с fuzzy search
+- [ ] Cmd+Shift+F показывает результаты поиска по содержимому
+- [ ] Все горячие клавиши из таблицы работают
+- [ ] Breadcrumb-навигация для текущего файла
+- [ ] Иконки файлов по типу в дереве и вкладках
+- [ ] File tree виртуализирован, скролл плавный
+
+## Оценка
+
+- **Надёжность решения: 7/10** -- виртуализация и search добавляют сложность, но библиотеки проверены.
+- **Уверенность: 7/10** -- много нового UI, но каждый компонент изолирован.
diff --git a/docs/iterations/edit-project/iter-5-git-watching.md b/docs/iterations/edit-project/iter-5-git-watching.md
new file mode 100644
index 00000000..d33f661d
--- /dev/null
+++ b/docs/iterations/edit-project/iter-5-git-watching.md
@@ -0,0 +1,154 @@
+# Итерация 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.
diff --git a/docs/iterations/edit-project/plan-architecture.md b/docs/iterations/edit-project/plan-architecture.md
new file mode 100644
index 00000000..33f8f4eb
--- /dev/null
+++ b/docs/iterations/edit-project/plan-architecture.md
@@ -0,0 +1,1295 @@
+# Архитектурный план: In-App Code Editor
+
+## Контекст
+
+На странице TeamDetailView рядом с путём проекта (`data.config.projectPath`, строка ~761 файла `TeamDetailView.tsx`) добавляется кнопка, открывающая полноэкранный редактор кода прямо внутри приложения. Редактор базируется на **CodeMirror 6** (уже используется в проекте -- 17 пакетов `@codemirror/*` в `package.json`), а **не** ProseMirror. Это решение основано на том, что CodeMirror -- единственный редактор кода в зависимостях проекта, с готовым набором языковых расширений и темой `oneDark`.
+
+## Оценки
+
+- **Надежность решения**: 8/10 -- CodeMirror 6 проверен в продакшене (VS Code web, Obsidian), все зависимости уже в проекте.
+- **Уверенность в плане**: 8/10 -- архитектура повторяет паттерны ChangeReviewDialog (full-screen overlay + file tree + CM editor).
+
+---
+
+## Архитектурная диаграмма (ASCII)
+
+```
+ ┌─────────────────────────────────────────┐
+ │ TeamDetailView.tsx │
+ │ [FolderOpen icon] [Edit button] ◄──────┤ Кнопка запуска
+ └───────────────────┬─────────────────────┘
+ │ open={true}
+ ┌───────────────────▼─────────────────────┐
+ │ CodeEditorOverlay (full-screen) │
+ │ ┌──────────────┐ ┌──────────────────┐ │
+ │ │ FileTreePanel│ │ EditorTabsPanel │ │
+ │ │ │ │ ┌────────────┐ │ │
+ │ │ ProjectTree │ │ │ EditorTab │ │ │
+ │ │ component │ │ │ EditorTab │ │ │
+ │ │ (recursive) │ │ └────────────┘ │ │
+ │ │ │ │ ┌────────────┐ │ │
+ │ │ │ │ │CodeMirror │ │ │
+ │ │ │ │ │EditorView │ │ │
+ │ └──────────────┘ │ └────────────┘ │ │
+ │ └──────────────────┘ │
+ └────────────────────────────────────────-┘
+ │ IPC
+ ┌──────────────▼──────────────────────────┐
+ │ Preload Bridge │
+ │ editor.readDir / readFile / writeFile │
+ │ editor.createFile / deleteFile │
+ └──────────────┬──────────────────────────┘
+ │
+ ┌──────────────▼──────────────────────────┐
+ │ Main Process: ProjectFileService │
+ │ (sandboxed path validation) │
+ │ ┌─────────────────────────────────┐ │
+ │ │ fs.readdir / fs.readFile / │ │
+ │ │ fs.writeFile / fs.unlink / │ │
+ │ │ fs.mkdir │ │
+ │ └─────────────────────────────────┘ │
+ └─────────────────────────────────────────┘
+```
+
+---
+
+## 1. Компонентная иерархия
+
+### 1.1 Новые компоненты
+
+Размещение: `src/renderer/components/team/editor/`
+
+```
+editor/
+├── CodeEditorOverlay.tsx # Полноэкранный overlay (аналог ChangeReviewDialog)
+├── FileTreePanel.tsx # Левая панель с деревом файлов
+├── FileTreeNode.tsx # Рекурсивная нода дерева (файл / директория)
+├── EditorTabsPanel.tsx # Правая панель: вкладки + CodeMirror
+├── EditorTab.tsx # Одна вкладка открытого файла
+├── CodeMirrorEditor.tsx # Обёртка CM6 для редактирования (не diff)
+├── EditorToolbar.tsx # Панель инструментов (Save, Undo, Redo, язык)
+├── EditorStatusBar.tsx # Status bar: Ln:Col, язык, отступы, кодировка (UX Review 17.1.4)
+├── EditorEmptyState.tsx # Пустое состояние (нет открытых файлов + shortcuts шпаргалка)
+├── EditorBinaryState.tsx # Заглушка для бинарных файлов (UX Review 17.1.6)
+└── EditorErrorState.tsx # Заглушка для ошибок чтения файла (UX Review 17.2.5)
+```
+
+### 1.2 Принцип Single Responsibility
+
+| Компонент | Ответственность |
+|-----------|----------------|
+| `CodeEditorOverlay` | Layout: fixed inset-0, z-50, header/close, split layout |
+| `FileTreePanel` | Загрузка дерева, expand/collapse, поиск, контекстное меню |
+| `FileTreeNode` | Рендер одной ноды, иконка, клик, drag |
+| `EditorTabsPanel` | Управление открытыми табами, переключение |
+| `CodeMirrorEditor` | CM6 lifecycle: create/destroy EditorView, extensions, keybindings |
+| `EditorToolbar` | Действия: Save (Cmd+S), язык, отступы, кодировка |
+
+### 1.3 Паттерн overlay (повтор ChangeReviewDialog)
+
+Вместо `