* fix: add retry logic to sendInboxMessage for concurrent writes On Windows, parallel writes to the same inbox file cause race conditions where atomicWrite verification fails (another process overwrites between write and verify). Added retry loop (8 attempts) matching the existing pattern in addTaskComment. Bumps teamctl version to 11. Fixes CI failure: test (windows-latest) "parallel messages to same inbox" * fix: enhance CLI installer and session management - Updated the postinstall script in package.json to handle rebuild failures gracefully. - Added clearContext option in team launch requests to allow starting fresh sessions without resuming previous context. - Improved CLI installer logging by integrating raw output chunks for better terminal rendering. - Refactored components to utilize TerminalLogPanel for displaying installation logs, enhancing user experience during CLI installation. - Updated various services and hooks to support the new clearContext feature and raw logging. * fix: update MemberBadge and LaunchTeamDialog components for improved functionality - Modified MemberBadge to display 'lead' for team leads instead of the full name. - Refactored LaunchTeamDialog to simplify model selection logic and replace the Select component with a custom button-based interface for better user experience. - Enhanced KanbanTaskCard to include meta actions for task management, improving the layout and functionality for manual review tasks. * feat: auto-publish releases with stable download links - Change releaseType from draft to release for auto-publishing - Add upload-stable-links job to create version-agnostic asset copies - Update README with direct download URLs per platform - Add Requirements section to Installation - Remove downloads/platform badges - Add docs/RELEASE.md with versioning and release guide - Move community docs to .github/ * improvemtns * improvement * fix: handle Windows spawn EINVAL on non-ASCII paths and add helper utilities * improvements * fix: enhance child process environment handling for Windows - Added a helper function to build the child process environment with the correct HOME directory, addressing issues with non-ASCII usernames on Windows. - Updated CLI installer methods to utilize the new environment setup for improved compatibility and error handling. * refactor: replace execFile and spawn with execCli and spawnCli in CLI and TeamProvisioning services - Updated CliInstallerService and TeamProvisioningService to use execCli and spawnCli for improved error handling and compatibility, particularly on Windows. - Enhanced child process utility functions to better manage non-ASCII paths and provide consistent behavior across different platforms. - Adjusted tests to mock new child process utilities and verify correct usage in service methods. * fix * fix windows * feat: add download badges with direct links per platform * refactor: move download buttons to Installation section * refactor: move Docker files to docker/ and CHANGELOG to docs/ * refactor: move vite.standalone.config to docker/, remove .nvmrc * refactor: merge tsconfig.test.json into tsconfig.json * refactor: remove .editorconfig, .gitattributes, merge knip.json into package.json * fix: adjust macOS download badge sizing * feat: implement in-app project editor with CodeMirror integration - Added architectural plan and iteration plan for the in-app project editor. - Introduced new components for the editor, including CodeEditorOverlay, FileTreePanel, and EditorTabsPanel. - Established state management using Zustand for editor state persistence. - Implemented IPC channels for file operations and editor functionality. - Enhanced TeamDetailView with a button to open the editor overlay. - Conducted reuse analysis for existing components to optimize codebase integration. * feat: enhance in-app project editor with architecture documentation and service updates - Added detailed architecture and component hierarchy documentation for the in-app project editor. - Introduced `ProjectFileService` to manage file operations with improved path validation. - Updated `electron.vite.config.ts` to set `UV_THREADPOOL_SIZE` for better performance on Windows. - Deferred non-critical startup tasks in `index.ts` to avoid thread pool contention. - Enhanced `CliInstallerService` with timeout handling for status gathering to prevent UI hangs. - Added tests for `CliInstallerService` to ensure proper timeout behavior. * 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. * feat: enhance project editor with autosave, improved file management, and performance optimizations - Implemented draft autosave functionality to prevent data loss during crashes, with recovery options for unsaved changes. - Updated file management services to support better path validation and conflict detection. - Enhanced performance with optimized file watcher and caching strategies for project scanning. - Improved user experience with confirmation dialogs for unsaved changes and refined keyboard shortcuts. - Documented testing strategies and rollback plans for iterative development. * feat: integrate simple-git for enhanced Git status tracking and improve file watcher performance - Replaced direct Git command usage with `simple-git` for more reliable status tracking, including support for renamed files and conflict detection. - Updated IPC channels to reflect changes in Git status retrieval method. - Enhanced file watcher initialization on Windows to prevent UV thread pool saturation by starting watchers sequentially. - Improved application startup by staggering context system initialization and notification listeners to optimize performance. * feat: optimize IPC initialization and context management for improved app performance - Deferred IPC-heavy initialization to occur after the first paint to prevent app freezing on Windows. - Staggered notification listener setup to avoid saturating the UV thread pool during startup. - Updated context system initialization to be lazy, ensuring local context is always ready without upfront costs. - Enhanced data fetching sequence to reduce simultaneous IPC calls, improving overall responsiveness. * 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. * feat: enhance TeamConfigReader with improved file handling and concurrency - Introduced `mapLimit` function to manage concurrent processing of team directories, optimizing performance. - Added `readFileHead` function to read the beginning of large configuration files efficiently. - Implemented `extractQuotedString` to safely extract values from JSON strings in configuration headers. - Enhanced error handling and validation for team configuration files, ensuring robust processing of team data. - Updated logic to handle large configuration files differently, improving overall reliability and performance. * feat: enhance team management with improved session and project path history handling - Introduced constants for maximum session and project path history limits to optimize memory usage. - Updated `TeamConfigReader` and `TeamProvisioningService` to limit session and project path history to defined maximums. - Enhanced `TeamMembersMetaStore` to handle large meta files more efficiently by checking file size before processing. - Refactored state management in `teamSlice` to include optimized lookups for team summaries by name and session ID. * feat: enhance GlobalTaskDetailDialog and TaskDetailDialog with loading state management - Added a loading state to the TaskDetailDialog to display a loading indicator while fetching team data. - Updated GlobalTaskDetailDialog to pass the loading state to TaskDetailDialog. - Modified the selectTeam function in teamSlice to accept options for skipping project auto-selection, improving team data handling. * feat: optimize team display name resolution and enhance file change handling - Introduced a caching mechanism for team display names to reduce redundant API calls and improve performance. - Updated file change event handling to debounce cache invalidation, preventing unnecessary rescans during rapid file changes. - Enhanced GlobalTaskDetailDialog and TaskDetailDialog to manage loading states and improve team data fetching logic. - Refactored teamSlice to streamline team selection and data loading processes. * fix: improve team selection logic and prevent duplicate fetches - Enhanced GlobalTaskDetailDialog to handle loading states more effectively, preventing unnecessary re-fetching of team data. - Updated selectTeam function in teamSlice to guard against duplicate in-flight fetches for the same team, improving performance and user experience. - Refactored dependencies in useEffect to ensure proper data loading behavior. * feat: enhance team data fetching with performance logging and timeout handling - Added performance logging to the handleGetData function, tracking the duration of team data retrieval and logging warnings for slow responses. - Implemented a timeout mechanism in the selectTeam function to prevent long-running fetch operations, improving user experience. - Enhanced the getTeamData method with detailed timing metrics for each data loading step, allowing for better performance analysis and debugging. * feat: implement timeout handling and logging for team data fetching - Added a timeout mechanism to the getBranch calls in TeamDataService to prevent hangs on Windows setups, improving reliability during team data retrieval. - Introduced performance logging in TeamDetailView and teamSlice to track the start and completion of team selection processes, enhancing debugging capabilities. - Updated error handling to provide clearer warnings during team provisioning and selection, improving user experience. * feat: enhance MarkdownViewer and task dialogs with loading state management and performance logging - Introduced character limits for Markdown content in MarkdownViewer to prevent UI freezes with large content. - Added state management for raw content display in MarkdownViewer, allowing users to expand and view large markdown files. - Implemented performance logging in GlobalTaskDetailDialog and TaskDetailDialog to track loading states and improve debugging. - Updated loading state handling in TaskDetailDialog to ensure accurate representation of loading conditions. * feat: enhance TaskCommentsSection with improved comment rendering and visibility management - Added state management for visible comments, allowing users to see a limited number of comments for better performance. - Implemented logic to cap the number of rendered comments, preventing UI freezes with large comment lists. - Introduced sorting for comments based on creation date to display the most recent comments first. - Updated the UI to inform users when only a subset of comments is being displayed, enhancing user experience. * feat: enhance MarkdownViewer with improved character limits and syntax highlighting management - Updated character limits for Markdown content to prevent UI freezes with large inputs. - Introduced logic to disable syntax highlighting for medium/large content and show raw previews for very large content. - Enhanced responsiveness of the MarkdownViewer by managing rendering based on content size. * refactor: remove console warnings from team-related components for cleaner logging - Eliminated console warnings in TeamDetailView, GlobalTaskDetailDialog, and TaskDetailDialog to streamline logging and reduce clutter during team data operations. - Updated the selectTeam function in teamSlice to remove unnecessary logging, enhancing performance and readability. * feat: add project editor with drag & drop file management - Backend: ProjectFileService with file CRUD, search, git status, file watcher - IPC: 12 editor channels with security validation and path containment - Store: editorSlice with multi-tab management, draft persistence, conflict detection - UI: CodeMirror 6 editor, file tree with DnD, search-in-files, context menus - Move: fs.rename with EXDEV fallback, full path remapping across all caches - Tests: comprehensive coverage for services, IPC handlers, store, and utilities * fix: rename closeTab/setActiveTab to closeEditorTab/setActiveEditorTab Resolve naming collision between editorSlice and tabSlice. Both slices defined closeTab and setActiveTab, and since editorSlice was spread last in the store composition, it silently overwrote the tabSlice methods, breaking tab management. * fix: editor improvements — isDir bug, scroll-to-line, Quick Open, a11y - Fix isDir heuristic: use backend-provided isDirectory instead of filename-based guessing (breaks for Makefile, .github, etc.) - Add scroll-to-line on search result click via editorPendingGoToLine - Add Cmd+Shift+W shortcut for toggling line wrap - Rewrite Quick Open to fetch all project files from backend API instead of flattening the loaded tree (limited to expanded dirs) - Fix fd leak in atomicWrite: close file handle in finally block - Add a11y: role=dialog/alert, aria-modal, aria-label on modals - Add type=button on error state buttons --------- Co-authored-by: Алексей <aleksei@example.com>
42 KiB
Анализ переиспользования кодовой базы для In-App Project Editor
1. Переиспользуемые компоненты
1.1 ReviewFileTree -- высокий потенциал извлечения
Файл: /Users/belief/dev/projects/claude/claude_team/src/renderer/components/team/review/ReviewFileTree.tsx
Это самый важный компонент для переиспользования. Внутри него:
buildTree(files)функция (строки 42-83) -- построение дерева из плоского списка путей. Алгоритм: разбивает пути по/, строит иерархиюTreeNode, коллапсирует одноуровневые папки (как в VS Code). Это универсальный алгоритм, не привязанный к review.TreeItemкомпонент (строки 147-264) -- рекурсивный рендеринг узла дерева с иконками, отступами, коллапсом папок.ReviewFileTree(строки 297-376) -- корневой компонент с auto-expand и auto-scroll.
Проблема: Сейчас ReviewFileTree жестко привязан к review-контексту:
TreeItemпринимаетhunkDecisions,fileDecisions,fileChunkCounts,viewedSet-- всё review-специфичноеFileStatusIconрендерит статусы review (accepted/rejected/mixed/pending)- Строки +/- в каждом файле (
linesAdded,linesRemoved)
Рекомендация: Извлечь generic FileTree из ReviewFileTree. Структура:
- Выделить
buildTree()иTreeNodeв утилитуsrc/renderer/utils/fileTreeBuilder.ts - Создать generic
FileTreeкомпонент сrenderItemcallback (render-prop для кастомизации правой части каждого файлового элемента) ReviewFileTreeстановится тонкой обёрткой вокругFileTreeс review-специфичнымrenderItemEditorFileTree-- вторая обёртка для редактора (показывает иконки по типу файла, dirty-маркер)
Оценка надёжности: 8/10 -- buildTree проверен в продакшене, алгоритм коллапса протестирован. Оценка уверенности: 9/10 -- это чистый extract-and-wrap рефакторинг.
1.2 CodeMirrorDiffView -- частичное переиспользование
Файл: /Users/belief/dev/projects/claude/claude_team/src/renderer/components/team/review/CodeMirrorDiffView.tsx
Этот компонент содержит ценную инфраструктуру:
getSyncLanguageExtension(fileName)(строки 64-123) -- маппинг расширений файлов на CodeMirror language extensions. 16+ языков. Должен быть извлечён в общую утилиту.getAsyncLanguageDesc(fileName)(строки 126-128) -- async fallback через@codemirror/language-data.diffTheme(строки 158-283) -- тема CodeMirror на CSS-переменных. Частично переиспользуема для обычного редактора (базовые стили.cm-gutters,.cm-content,.cm-scroller).langCompartmentпаттерн -- Compartment для ленивой инжекции языка. Полностью переиспользуем.buildExtensions()(строки 477-688) -- настройка расширений. Для редактора нужна упрощённая версия (без merge/diff, без hunk navigation).
Что НЕ переиспользуется: Вся diff/merge логика (unifiedMergeView, mergeCompartment, chunk navigation, merge toolbar) -- это 60%+ кода компонента.
Рекомендация: Создать CodeMirrorEditor компонент (без diff) рядом или вместо fork'а CodeMirrorDiffView:
- Извлечь
getLanguageExtension()вsrc/renderer/utils/codemirrorLanguages.ts - Извлечь базовую тему в
src/renderer/utils/codemirrorTheme.ts - Новый
CodeMirrorEditorиспользует эти утилиты +@codemirror/autocomplete(уже вpackage.json!)
Оценка надёжности: 7/10 -- ядро проверено, но отделение от diff-логики требует внимания. Оценка уверенности: 8/10 -- чётко понятно что извлекать.
1.3 ChangeReviewDialog -- паттерн layout
Файл: /Users/belief/dev/projects/claude/claude_team/src/renderer/components/team/review/ChangeReviewDialog.tsx
Это полноэкранный overlay (не Radix Dialog!). Паттерн (строки 507-676):
fixed inset-0 z-50 flex flex-col bg-surface
├── Header (border-b, bg-surface-sidebar, macOS traffic-light padding)
├── Toolbar (border-b)
└── Content (flex flex-1 overflow-hidden)
├── Sidebar (w-64, overflow-y-auto, border-r, bg-surface-sidebar)
└── Main content area (flex-1)
Что переиспользуется:
- Layout паттерн: header + sidebar + content
- macOS traffic-light padding (
--macos-traffic-light-padding-left,WebkitAppRegion: 'drag') - Escape-to-close (строки 346-353)
- Loading/Error/Empty states (строки 586-673)
Рекомендация: Создать FullScreenPanel layout-компонент, который предоставляет:
- Header slot с macOS-safe padding
- Optional sidebar slot
- Content slot
- Escape-to-close behaviour
- Loading/Error/Empty state handling
Или проще -- просто скопировать layout-паттерн в ProjectEditor, а рефакторить в общий компонент потом.
Оценка надёжности: 7/10 Оценка уверенности: 7/10 -- зависит от того, насколько сильно отличается layout редактора.
1.4 DiffErrorBoundary -- прямое переиспользование
Файл: /Users/belief/dev/projects/claude/claude_team/src/renderer/components/team/review/DiffErrorBoundary.tsx
Специализированный error boundary для diff-view. Нужен аналогичный для CodeMirror editor. Можно обобщить:
- Переименовать в
EditorErrorBoundary - Убрать diff-специфичные пропы (
oldString,newString) - Добавить generic error info display
Оценка надёжности: 9/10 Оценка уверенности: 9/10
1.5 UI примитивы
Прямое переиспользование без изменений:
| Компонент | Путь | Применение |
|---|---|---|
ErrorBoundary |
src/renderer/components/common/ErrorBoundary.tsx |
Обёртка всего редактора |
CopyablePath |
src/renderer/components/common/CopyablePath.tsx |
Путь к файлу в header |
CopyButton |
src/renderer/components/common/CopyButton.tsx |
Копирование содержимого |
ConfirmDialog |
src/renderer/components/common/ConfirmDialog.tsx |
"Save before close?" |
Tooltip |
src/renderer/components/ui/tooltip.tsx |
Тултипы на кнопках toolbar |
Button |
src/renderer/components/ui/button.tsx |
Кнопки toolbar |
Dialog |
src/renderer/components/ui/dialog.tsx |
Мелкие модалки (settings) |
Tabs |
src/renderer/components/ui/tabs.tsx |
Табы открытых файлов |
1.6 Компоненты review, которые НЕ стоит переиспользовать
ReviewToolbar-- слишком review-специфичен (accept/reject/apply counters)ContinuousScrollView-- scroll-spy для diff-review, не подходит для редактораFileSectionDiff/FileSectionHeader-- привязаны к diff workflowViewedProgressBar-- review-onlyConflictDialog-- review-only
2. Существующие IPC каналы
2.1 Уже есть -- файловые операции
| Канал | Файл | Что делает | Применимость |
|---|---|---|---|
review:saveEditedFile |
src/main/ipc/review.ts |
Сохраняет файл на диск (filePath, content) |
УЯЗВИМОСТЬ: нет валидации пути! НЕ переиспользовать без исправления (см. SEC-11). Для editor -- отдельный канал с валидацией |
review:getFileContent |
src/main/ipc/review.ts |
Читает файл + original + modified | Частично -- нужна упрощённая версия |
read-mentioned-file |
src/main/ipc/utility.ts |
Читает файл по абсолютному пути с валидацией | Можно использовать, но ограничен maxTokens |
shell:openPath |
src/main/ipc/utility.ts |
Открывает файл в системном приложении | "Open in external editor" |
shell:showInFolder |
src/main/ipc/utility.ts |
Показывает файл в Finder | "Reveal in Finder" |
2.2 Чего НЕТ -- нужно создать
Для полноценного редактора проекта нужны новые IPC каналы:
editor:listDirectory(dirPath)-- рекурсивный listing файлов (с ignore-паттернами:.git,node_modules, etc.)editor:readFile(filePath)-- чтение файла без ограниченийmaxTokens(в отличие отread-mentioned-file)editor:saveFile(filePath, content)-- можно переиспользоватьreview:saveEditedFile, но лучше отдельный канал с более широкой валидациейeditor:createFile(filePath, content?)-- создание нового файлаeditor:deleteFile(filePath)-- удаление файла (сconfirmна renderer стороне)editor:renameFile(oldPath, newPath)-- переименованиеeditor:watchDirectory(dirPath)-- подписка на изменения в директории (для обновления file tree)
Паттерн регистрации (из src/main/ipc/review.ts):
// Module-level state + guard
let service: EditorService | null = null;
function getService(): EditorService { ... }
// Forward-compatible config object
export interface EditorHandlerDeps { ... }
export function initializeEditorHandlers(deps: EditorHandlerDeps): void { ... }
export function registerEditorHandlers(ipcMain: IpcMain): void { ... }
export function removeEditorHandlers(ipcMain: IpcMain): void { ... }
Каналы в ipcChannels.ts -- плоские export const, НЕ объект (подтверждено в MEMORY.md).
Оценка надёжности: 8/10 -- паттерн отработан на 20+ модулях. Оценка уверенности: 9/10
3. Zustand-паттерн для Editor Slice
3.1 Существующий паттерн slice'ов
Файл: /Users/belief/dev/projects/claude/claude_team/src/renderer/store/types.ts
18 slice'ов, объединённых через intersection type. Каждый slice:
export interface SomeSlice {
// Data
someData: T[];
selectedId: string | null;
loading: boolean;
error: string | null;
// Actions
fetchData: () => Promise<void>;
selectItem: (id: string | null) => void;
}
export const createSomeSlice: StateCreator<AppState, [], [], SomeSlice> = (set, get) => (
// initial state + actions
});
3.2 Рекомендуемая структура EditorSlice
export interface EditorSlice {
// State
editorProjectPath: string | null; // Текущий проект
editorFileTree: FileTreeNode[]; // Дерево файлов
editorFileTreeLoading: boolean;
editorOpenFiles: OpenFile[]; // Открытые файлы (табы)
editorActiveFilePath: string | null; // Активный файл
editorDirtyFiles: Set<string>; // Файлы с несохранёнными изменениями
editorError: string | null;
// File content cache (path -> content)
editorFileContents: Record<string, string>;
editorFileContentsLoading: Record<string, boolean>;
// Actions
openEditor: (projectPath: string) => Promise<void>;
closeEditor: () => void;
loadFileTree: (dirPath: string) => Promise<void>;
openFile: (filePath: string) => Promise<void>;
closeFile: (filePath: string) => void;
setActiveFile: (filePath: string) => void;
updateFileContent: (filePath: string, content: string) => void;
saveFile: (filePath: string) => Promise<void>;
saveAllDirty: () => Promise<void>;
}
Важно: Следовать правилу из CLAUDE.md -- "Store over Props": дочерние компоненты читают из store напрямую через useStore().
Куда добавить:
src/renderer/store/slices/editorSlice.ts-- новый slice- Добавить
EditorSliceвAppStatetype вtypes.ts - Добавить
...createEditorSlice(...args)вstore/index.ts
Оценка надёжности: 9/10 Оценка уверенности: 9/10
3.3 Ближайший аналог -- changeReviewSlice
Файл: /Users/belief/dev/projects/claude/claude_team/src/renderer/store/slices/changeReviewSlice.ts
Этот slice ближе всего к будущему editorSlice:
fileContents: Record<string, FileChangeWithContent>-- кеш содержимого файловfileContentsLoading: Record<string, boolean>-- состояние загрузки per-fileeditedContents: Record<string, string>-- несохранённые измененияsaveEditedFile(filePath)-- сохранение на дискdiscardFileEdits(filePath)-- отмена изменений- Debounced persistence
4. CSS/Theme -- переиспользование
4.1 Существующие CSS-переменные
Файл: /Users/belief/dev/projects/claude/claude_team/src/renderer/index.css
Полностью подходят для редактора:
| Категория | Переменные | Применение в редакторе |
|---|---|---|
| Surfaces | --color-surface, --color-surface-raised, --color-surface-sidebar |
Фон редактора, sidebar, header |
| Borders | --color-border, --color-border-subtle, --color-border-emphasis |
Разделители панелей |
| Text | --color-text, --color-text-secondary, --color-text-muted |
Текст в file tree, status bar |
| Code | --code-bg, --code-border, --code-line-number, --code-filename |
Фон редактора, номера строк |
| Syntax | --syntax-string, --syntax-comment, --syntax-keyword и т.д. |
Подсветка синтаксиса |
| Inline code | --inline-code-bg, --inline-code-text |
Инлайн код в markdown |
| Scrollbar | --scrollbar-thumb, --scrollbar-thumb-hover |
Скроллбар в file tree |
| Card | --card-bg, --card-border, --card-header-bg |
Панели, headers |
| Skeleton | --skeleton-base, --skeleton-base-light |
Loading state |
4.2 Тема CodeMirror
diffTheme в CodeMirrorDiffView.tsx (строки 158-283) уже использует CSS-переменные:
'&': {
backgroundColor: 'var(--color-surface)',
color: 'var(--color-text)',
fontFamily: 'ui-monospace, SFMono-Regular, ...',
fontSize: '13px',
},
'.cm-gutters': {
backgroundColor: 'var(--color-surface)',
borderRight: '1px solid var(--color-border)',
...
}
Нужно извлечь базовую тему (без diff-стилей .cm-changedLine, .cm-deletedChunk и т.д.) -- примерно 40% от текущей темы.
4.3 Light theme
Поддержка есть через :root.light override'ы в index.css. Если diffTheme использует CSS-переменные (а он использует), то light theme заработает автоматически.
5. CodeMirror vs ProseMirror
5.1 CodeMirror 6 -- уже в проекте
Из package.json:
"@codemirror/autocomplete": "^6.20.0",
"@codemirror/commands": "^6.10.2",
"@codemirror/lang-cpp", "@codemirror/lang-css", "@codemirror/lang-go",
"@codemirror/lang-html", "@codemirror/lang-java", "@codemirror/lang-javascript",
"@codemirror/lang-json", "@codemirror/lang-less", "@codemirror/lang-markdown",
"@codemirror/lang-php", "@codemirror/lang-python", "@codemirror/lang-rust",
"@codemirror/lang-sass", "@codemirror/lang-sql", "@codemirror/lang-xml",
"@codemirror/lang-yaml",
"@codemirror/language", "@codemirror/language-data",
"@codemirror/merge", "@codemirror/state",
"@codemirror/theme-one-dark", "@codemirror/view"
Это 16 языковых пакетов + @codemirror/language-data (ещё ~30 языков async). Плюс @codemirror/autocomplete уже установлен.
5.2 Рекомендация: ТОЛЬКО CodeMirror 6
Однозначно CodeMirror 6, НЕ ProseMirror. Причины:
- Уже 20+ пакетов CodeMirror в зависимостях -- нулевой overhead по bundle size
- Работающая инфраструктура:
getSyncLanguageExtension(),getAsyncLanguageDesc(), тема, Compartment-паттерн -- всё протестировано в production @codemirror/autocompleteуже установлен -- автодополнение из коробки- CodeMirror = код-редактор, ProseMirror = rich text / WYSIWYG. Для проектного редактора нужен именно код-редактор
- ProseMirror добавил бы ~150-200KB в bundle + совершенно новая экосистема плагинов
Не нужно добавлять НИКАКИХ новых зависимостей для базового редактора. Всё есть.
Оценка надёжности: 10/10 -- CodeMirror 6 зрелый, используется в VSCode, Chrome DevTools Оценка уверенности: 10/10 -- ProseMirror для code editing = антипаттерн
6. Anti-patterns и риски
6.1 Размер компонентов
Проблема: ChangeReviewDialog.tsx -- 677 строк. CodeMirrorDiffView.tsx -- 809 строк. Оба на грани допустимого.
Рекомендация для Editor:
ProjectEditor.tsx-- max 150 строк (layout shell, делегирует всё дочерним)EditorFileTree.tsx-- max 200 строкEditorTabBar.tsx-- max 100 строкEditorCodePane.tsx-- max 150 строк (обёртка вокруг CodeMirror)EditorToolbar.tsx-- max 100 строк- Хуки (
useEditorKeyboard,useEditorFileOps) -- по 50-100 строк
6.2 Performance с большими файлами
Проблема: CodeMirror 6 virtual scrolling работает, но:
- Файлы >5MB могут замедлить парсинг языка
readFileчерез IPC сериализует содержимое как JSON string -- большие файлы замедляют IPC
Рекомендация:
- Лимит чтения: ~2MB (показывать "File too large, open externally")
EditorView.scrollPastEnd-- чтобы пользователь мог скроллить ниже конца файла- Lazy language loading через Compartment (уже реализовано в
CodeMirrorDiffView)
6.3 Dirty state и unsaved changes
Проблема: changeReviewSlice хранит editedContents как Record<string, string> -- весь контент файла в памяти per-dirty-file. При 10+ грязных файлах это может быть гигабайт RAM.
Рекомендация:
- Хранить ТОЛЬКО для активного файла + 2-3 соседних табов (LRU cache)
- Для остальных -- хранить
EditorStateобъект CodeMirror (он уже в памяти CM) - При переключении табов -- сохранять
EditorState(включая undo history), не строку
6.4 File watching race conditions
Проблема: Если пользователь редактирует файл в нашем редакторе, а CLI-агент одновременно меняет его через review:saveEditedFile -- конфликт.
Рекомендация:
mtimecheck перед записью (какreview:checkConflict)- Уведомление "File changed on disk" с выбором (reload / keep mine / show diff)
6.5 Missing error boundaries
Проблема: ErrorBoundary в common/ -- один на всё приложение. DiffErrorBoundary -- только для diff. Если CodeMirror крашится в editor mode, нужен отдельный boundary.
Рекомендация: Обернуть CodeMirrorEditor в специализированный EditorErrorBoundary (можно обобщить DiffErrorBoundary).
6.6 IPC parameter validation
Проблема (CRITICAL): В review.ts IPC handler handleSaveEditedFile НЕ валидирует путь -- прямой writeFile() без validateFilePath(). Это существующая уязвимость (см. секцию 10.3).
Рекомендация:
- ВСЕ IPC handlers, работающие с файлами, ОБЯЗАНЫ вызывать
validateFilePath()изsrc/main/utils/pathValidation.ts - Для editor: выделенный module-level
activeProjectRoot, не принимаемый от renderer при каждом вызове - Дополнительно:
validateFileName()для создания файлов,isDevicePath()для блокировки device files, запрет записи в.git/ - Подробный чеклист -- в
plan-architecture.mdсекция 18
7. Итоговая архитектурная рекомендация
Что ИЗВЛЕЧЬ из существующего кода (рефакторинг):
buildTree()+TreeNode-->src/renderer/utils/fileTreeBuilder.tsgetSyncLanguageExtension()+getAsyncLanguageDesc()-->src/renderer/utils/codemirrorLanguages.ts- Базовая CM тема (без diff) -->
src/renderer/utils/codemirrorTheme.ts ReviewFileTree--> genericFileTree+ReviewFileTreewrapper
Что СОЗДАТЬ с нуля:
src/renderer/store/slices/editorSlice.tssrc/main/ipc/editor.ts+ handler'ыsrc/preload/constants/ipcChannels.ts-- добавитьEDITOR_*каналыsrc/preload/index.ts-- добавитьeditorAPIsrc/renderer/components/editor/-- компоненты редактораsrc/main/services/editor/EditorService.ts-- сервис файловых операций
Что ПЕРЕИСПОЛЬЗОВАТЬ напрямую:
- Все UI примитивы из
components/ui/ ErrorBoundary,ConfirmDialog,CopyablePath,CopyButton- CSS-переменные (100% готовы)
- CodeMirror 6 пакеты (все 20+ уже в зависимостях)
wrapHandler<T>()паттерн для IPC- Zustand slice pattern
8. Архитектурная ревизия: дополнения к reuse-анализу
Добавлено после ревизии. Конкретизирует что именно извлекать и как.
8.1 Обязательные рефакторинги перед реализацией
Эти рефакторинги -- не optional. Без них будет дублирование кода, нарушающее DRY:
| Что извлечь | Откуда | Куда | Строки |
|---|---|---|---|
buildTree() + collapse() + сортировка |
ReviewFileTree.tsx:42-83 |
src/renderer/utils/fileTreeBuilder.ts |
~50 LOC |
getSyncLanguageExtension() + getAsyncLanguageDesc() |
CodeMirrorDiffView.tsx:64-128 |
src/renderer/utils/codemirrorLanguages.ts |
~70 LOC |
| Базовая тема CM (без diff-стилей) | CodeMirrorDiffView.tsx:158-198 |
src/renderer/utils/codemirrorTheme.ts |
~40 LOC |
wrapReviewHandler<T>() |
review.ts:133-145 |
src/main/ipc/ipcWrapper.ts |
~15 LOC |
Порядок: Рефакторинги 1-4 выполняются ПЕРЕД написанием нового кода итерации 1.
ReviewFileTree.tsx и CodeMirrorDiffView.tsx начинают импортировать из новых утилит.
Тесты этих компонентов должны продолжать проходить (zero behavior change).
8.2 Расхождения между файлами планов (исправлены)
| Расхождение | plan-architecture.md | plan-iterations.md | Решение |
|---|---|---|---|
| Имя сервиса | FileEditorService |
ProjectFileService |
ProjectFileService |
| Stateful/Stateless | constructor(rootPath) |
Не указано | Stateless, projectRoot как аргумент |
| Security | Свой assertInsideRoot() |
validateFilePath() |
validateFilePath() из pathValidation.ts |
| editorSlice в итерации 1 | Да | Нет (хук useEditorState) |
Нет slice в итерации 1, useState достаточно |
useEditorState.ts хук |
Не упомянут | Создаётся в итерации 2 | Убран, вся логика в slice |
| Overlay name | CodeEditorOverlay |
ProjectEditorOverlay |
ProjectEditorOverlay (лучше отражает scope) |
8.3 Review FileTree: конкретный план generic extraction
Текущий ReviewFileTree.tsx (~377 строк) содержит:
TreeNodeтип -- generic (name, fullPath, isFile, children, file?)buildTree()-- generic (принимаетfilesс.relativePath)collapse()-- generic (одноуровневый collapse)TreeItem-- review-specific (FileStatusIcon, +/- lines, viewedSet, hunkDecisions)getFileStatus()-- review-specificReviewFileTree-- review-specific (reads from store: hunkDecisions, fileDecisions)
Plan для generic FileTree:
src/renderer/utils/fileTreeBuilder.ts:
- export type TreeNode<T = unknown> = { name, fullPath, isFile, data?: T, children }
- export function buildTree<T>(items: T[], getRelativePath: (item: T) => string): TreeNode<T>[]
- export function sortTreeNodes<T>(nodes: TreeNode<T>[]): TreeNode<T>[]
src/renderer/components/common/FileTree.tsx:
- Generic FileTree<T> component
- Props: nodes, activeNodePath, onNodeClick, renderNodeExtra?, renderNodeIcon?
- Internal: TreeItem (renders folder/file, delegation через render-props)
- Handles: collapsedFolders, toggleFolder, auto-expand ancestors, auto-scroll
src/renderer/components/team/review/ReviewFileTree.tsx:
- Thin wrapper around FileTree<FileChangeSummary>
- Provides renderNodeExtra with FileStatusIcon + +/- lines
- Reads hunkDecisions/fileDecisions from store
src/renderer/components/team/editor/EditorFileTree.tsx:
- Thin wrapper around FileTree<FileTreeEntry>
- Provides renderNodeExtra with dirty marker
- Provides renderNodeIcon with file type icons
- Context menu integration
8.4 SOLID compliance checklist
- SRP: FileTreePanel -- UI only, data loading in slice
- SRP: CodeMirrorEditor -- lifecycle only, extensions in builder
- OCP: FileTree -- generic with render-props
- LSP: FileTreeNode extends FileTreeEntry (no field duplication)
- ISP: EditorSlice split into 4 documented groups
- DIP: Extensions via factory, not hardcoded in component
- DRY: buildTree, language detection, theme, wrapHandler -- all extracted
- Clean Architecture: dependency flow verified, no backward deps
9. UX Review: дополнения к reuse-анализу
Добавлено после UX-ревью. Что ещё нужно переиспользовать/создать для качественного UX.
9.1 Дополнительные компоненты для переиспользования
| Компонент | Путь | Применение в редакторе |
|---|---|---|
KeyboardShortcutsHelp |
review/KeyboardShortcutsHelp.tsx |
Модальное окно со списком shortcuts (кнопка ? в header) |
confirm() imperative API |
common/ConfirmDialog.tsx |
"Save before close?" при Escape с unsaved changes |
9.2 Новые утилиты, вызванные UX-требованиями
| Утилита | Путь | Зачем |
|---|---|---|
tabLabelDisambiguation.ts |
src/renderer/utils/ |
Показ "(main/utils)" для дублей index.ts в табах |
binaryDetector.ts |
src/main/utils/ |
Определение бинарных файлов (null bytes в первых 8KB) |
9.3 Новые компоненты, вызванные UX-требованиями
| Компонент | Описание |
|---|---|
EditorStatusBar.tsx |
Нижняя полоска: Ln:Col, язык, отступы, кодировка |
EditorBinaryState.tsx |
Заглушка для бинарных файлов вместо CM6 |
EditorErrorState.tsx |
Заглушка для файлов с ошибкой чтения (EACCES, ENOENT) |
EditorShortcutsHelp.tsx |
Модальное окно shortcuts (или переиспользовать KeyboardShortcutsHelp) |
9.4 CSS-переменные -- что уже есть, чего не хватает
Уже есть (полностью достаточно):
--color-surface,--color-surface-sidebar,--color-surface-raised-- для background--color-border,--color-border-subtle,--color-border-emphasis-- для разделителей--color-text,--color-text-secondary,--color-text-muted-- для текста--code-*,--syntax-*-- для CodeMirror--scrollbar-*-- для скроллбара--card-*-- для панелей
Не хватает (рекомендация: добавить в :root в index.css):
/* Editor-specific */
--editor-tab-active-bg: var(--color-surface);
--editor-tab-inactive-bg: var(--color-surface-sidebar);
--editor-tab-modified-dot: #f59e0b; /* amber для modified indicator */
--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);
Это обеспечит консистентность с остальными CSS-переменными проекта и лёгкую кастомизацию.
9.5 Accessibility -- что переиспользовать из существующего
ReviewFileTree.tsx (строка 232) имеет aria-label на expand/collapse. Это МИНИМУМ. При извлечении generic FileTree нужно сразу добавить:
role="tree"на корневой<ul>role="treeitem"+aria-expandedна каждой папкеrole="group"на вложенных<ul>role="treeitem"+aria-selectedна файлах- Keyboard navigation (arrow keys) -- в
FileTree, не в обёртках
Это не "nice to have" -- это требование WCAG 2.1 Level A для tree view.
10. Security Review: дополнения к reuse-анализу
Полный аудит безопасности описан в
plan-architecture.mdсекция 18. Здесь -- что из существующего кода переиспользовать для безопасности, и обнаруженные проблемы в текущем коде.
10.1 Переиспользуемые security-утилиты
| Утилита | Путь | Что делает | Как использовать |
|---|---|---|---|
validateFilePath() |
src/main/utils/pathValidation.ts |
Path traversal, symlink escape, sensitive patterns | КАЖДЫЙ IPC handler ОБЯЗАН вызывать |
SENSITIVE_PATTERNS |
src/main/utils/pathValidation.ts |
Regex-массив: .env, .ssh, *.key, *.pem и т.д. |
Автоматически через validateFilePath() |
resolveRealPathIfExists() |
src/main/utils/pathValidation.ts |
fs.realpathSync.native() с обработкой ENOENT |
Автоматически через validateFilePath() |
isPathWithinAllowedDirectories() |
src/main/utils/pathValidation.ts |
Containment check с cross-platform support | Автоматически через validateFilePath() |
isPathContained() |
src/main/ipc/validation.ts |
Простая containment check (normalize + startsWith) | НЕ использовать отдельно -- validateFilePath полнее |
10.2 Чего НЕ хватает в существующих утилитах (нужно создать для editor)
| Утилита | Описание | Зачем |
|---|---|---|
validateFileName(name) |
Валидация имени файла при создании | Запрет ., .., control chars, path separators, NUL, length > 255 |
isDevicePath(path) |
Проверка на /dev/, /proc/, /sys/ |
Блокировка device files до fs.readFile() |
isGitInternalPath(path) |
Проверка на .git/ в пути |
Запрет записи в .git/ (чтение -- ОК) |
atomicWriteFile(path, content) |
Atomic write через tmp + rename | Защита от corrupt при crash/disk full |
Рекомендация: добавить в src/main/utils/pathValidation.ts (validateFileName, isDevicePath, isGitInternalPath) и src/main/utils/atomicWrite.ts (atomicWriteFile).
10.3 Обнаруженная уязвимость в review.ts (Critical, existing!)
При анализе review.ts (секция 2.1 reuse-анализа) обнаружена уязвимость:
handleSaveEditedFile (строка 254 review.ts) принимает filePath от renderer и передаёт в ReviewApplierService.saveEditedFile() (строка 320 ReviewApplierService.ts), который вызывает writeFile(filePath, content, 'utf8') БЕЗ КАКОЙ-ЛИБО ВАЛИДАЦИИ ПУТИ.
Текущий код:
// review.ts:254
async function handleSaveEditedFile(_event, filePath, content) {
if (!filePath || typeof content !== 'string') {
return { success: false, error: 'Invalid parameters' };
}
// УЯЗВИМОСТЬ: filePath НЕ проверяется через validateFilePath()
return wrapReviewHandler('saveEditedFile', async () => {
const result = await getApplier().saveEditedFile(filePath, content);
// ...
});
}
// ReviewApplierService.ts:320
async saveEditedFile(filePath: string, content: string) {
// УЯЗВИМОСТЬ: прямая запись без валидации
await writeFile(filePath, content, 'utf8');
return { success: true };
}
Импакт: Скомпрометированный renderer может записать произвольный файл куда угодно в ФС.
Решение: Добавить validateFilePath(filePath, projectRoot) в handleSaveEditedFile. Нужен hotfix НЕЗАВИСИМО от editor-фичи.
10.4 Security-паттерн для editor IPC (обязательный)
// src/main/ipc/editor.ts -- каждый handler ОБЯЗАН следовать этому паттерну:
let activeProjectRoot: string | null = null; // module-level, set by editor:open
async function handleEditorReadFile(
_event: IpcMainInvokeEvent,
filePath: string // от renderer
): Promise<IpcResult<ReadFileResult>> {
return wrapHandler('readFile', async () => {
if (!activeProjectRoot) throw new Error('Editor not initialized');
// 1. Path validation (traversal, sensitive, symlink)
const validation = validateFilePath(filePath, activeProjectRoot);
if (!validation.valid) throw new Error(validation.error!);
// 2. Device path block
if (isDevicePath(validation.normalizedPath!)) throw new Error('Device files blocked');
// 3. File type check
const stats = await fs.lstat(validation.normalizedPath!);
if (!stats.isFile()) throw new Error('Not a regular file');
// 4. Size check
if (stats.size > MAX_FILE_SIZE) throw new Error('File too large');
// 5. Read
const content = await fs.readFile(validation.normalizedPath!, 'utf8');
// 6. Post-read TOCTOU verify
const realPath = await fs.realpath(validation.normalizedPath!);
const postValidation = validateFilePath(realPath, activeProjectRoot);
if (!postValidation.valid) throw new Error('Path changed during read');
return { content, size: stats.size, truncated: false, encoding: 'utf-8' };
});
}
Critical Files for Implementation
List 3-5 files most critical for implementing this plan:
/Users/belief/dev/projects/claude/claude_team/src/renderer/components/team/review/ReviewFileTree.tsx- FileTree logic to extract (buildTree algorithm, TreeNode type, collapse/expand)/Users/belief/dev/projects/claude/claude_team/src/renderer/components/team/review/CodeMirrorDiffView.tsx- CodeMirror infrastructure to extract (language detection, theme, Compartment pattern)/Users/belief/dev/projects/claude/claude_team/src/renderer/store/slices/changeReviewSlice.ts- Pattern to follow for editorSlice (fileContents cache, editedContents, saveEditedFile)/Users/belief/dev/projects/claude/claude_team/src/main/ipc/review.ts- IPC handler pattern to follow (wrapHandler, module-level state, deps injection) + EXISTING VULNERABILITY in saveEditedFile/Users/belief/dev/projects/claude/claude_team/src/main/utils/pathValidation.ts- Security validation to REUSE (not rewrite) -- validateFilePath, SENSITIVE_PATTERNS, symlink resolution
Performance-Critical Reuse Notes
Дополнение после Performance Review (plan-architecture.md секция 19). Конкретные performance-аспекты при переиспользовании кода.
CodeMirrorDiffView -- что НЕ копировать
editorViewMapRef из ChangeReviewDialog (строка 91) хранит Map<string, EditorView> для всех видимых файлов в continuous scroll view. Это допустимо для review (10-50 файлов одновременно), но НЕДОПУСТИМО для editor с 20+ табами.
Для editor использовать EditorState pooling:
// ПРАВИЛЬНО для editor:
const stateCache = useRef(new Map<string, EditorState>());
const viewRef = useRef<EditorView | null>(null);
// При переключении таба:
stateCache.current.set(oldTabId, viewRef.current!.state);
viewRef.current!.destroy();
viewRef.current = new EditorView({
state: stateCache.current.get(newTabId)!,
parent: containerRef.current!,
});
Паттерн initialState из CodeMirrorDiffView (строка 56, 699-705) -- это именно то, что нужно.
changeReviewSlice -- что НЕ копировать
editedContents: Record<string, string> (строка 74) хранит полный текст каждого редактированного файла в Zustand. В review это терпимо (изменения применяются и сбрасываются). Для editor каждый keystroke вызывает set() с новым Record -- все Zustand-подписчики перерисовываются.
Для editor контент живёт только в EditorState, не в Zustand. В store хранить:
editorModifiedFiles: Set<string> // dirty flags, не содержимое
@tanstack/react-virtual -- использовать для FileTree
Уже в проекте. Примеры:
DateGroupedSessions.tsx-- виртуализация списка сессийChatHistory.tsx-- виртуализация чатаNotificationsView.tsx-- виртуализация уведомлений
Для FileTree (итерация 4): flattenTree() -> FlatNode[] + useVirtualizer().
MembersJsonEditor -- правильный lifecycle паттерн
MembersJsonEditor.tsx (строки 27-73) -- образцовый паттерн для editor:
EditorState.create()с extensionsnew EditorView({ state, parent })-- один раз при mountview.destroy()-- в cleanup useEffect- Обновление doc через
view.dispatch({ changes: ... })-- при prop change onChangeRef.current = onChange-- для callback без re-create view
Этот паттерн масштабировать до EditorState pooling (Map вместо одного state).