agent-ecosystem/docs/research/split-screen-multi-view.md
iliya 5da9e2372d feat: enhance cross-team messaging and message storage
- Introduced new parameters for cross-team messaging, including CROSS_TEAM_SENT_SOURCE for better tracking of sent messages.
- Updated sendCrossTeamMessage function to append sent messages to the message store, ensuring a complete history of communications.
- Enhanced tests to validate the new message storage functionality and ensure accurate retrieval of sent messages.
- Improved handling of message timestamps and deduplication logic for cross-team communications.
2026-03-11 00:33:17 +02:00

210 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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

# Split Screen Multi-View Research
> Исследование: поддержка одновременного просмотра нескольких сессий/команд в split pane.
> Дата: 2026-03-10
## Текущее состояние архитектуры
### Split Pane System (уже реализовано)
- До **4 панелей** одновременно (`MAX_PANES = 4` в `src/renderer/types/panes.ts`)
- Drag-and-drop между панелями (dnd-kit, `TabbedLayout.tsx`)
- Resize handles между панелями (`PaneResizeHandle.tsx`)
- CSS `display: none` toggle — все вкладки mounted, только active видна (`PaneContent.tsx`)
- `TabUIContext` предоставляет `tabId` потомкам
### Pane Layout Structure
```typescript
// src/renderer/types/panes.ts
interface Pane {
id: string;
tabs: Tab[];
activeTabId: string;
selectedTabIds: string[];
widthFraction: number; // 0-1, сумма всех = 1.0
}
interface PaneLayout {
panes: Pane[];
focusedPaneId: string; // какая панель в фокусе
}
```
### Backward Compatibility Facade
Root-level `openTabs`, `activeTabId`, `selectedTabIds` синхронизируются из **focused pane only** через `syncFromLayout()` в `tabSlice.ts`.
---
## Изоляция состояния: что per-tab vs глобальное
### ✅ Per-Tab (уже изолировано)
| Состояние | Хранение | Слайс |
|-----------|----------|-------|
| UI expansion state | `tabUIStates[tabId]` | `tabUISlice` |
| Scroll position | `tabUIStates[tabId].savedScrollTop` | `tabUISlice` |
| Context panel visibility | `tabUIStates[tabId].showContextPanel` | `tabUISlice` |
| Context phase selection | `tabUIStates[tabId].selectedContextPhase` | `tabUISlice` |
| Session data cache | `tabSessionData[tabId]` | `sessionDetailSlice` |
| Conversation cache | `tabSessionData[tabId].conversation` | `sessionDetailSlice` |
**Паттерн чтения:**
```typescript
const stats = useStore((s) => {
const td = tabId ? s.tabSessionData[tabId] : null;
return td?.sessionClaudeMdStats ?? s.sessionClaudeMdStats;
});
```
### ❌ Глобальное (проблемы для multi-view)
| Состояние | Слайс | Проблема |
|-----------|-------|----------|
| `selectedTeamName` | `teamSlice` | Одна команда на всё приложение |
| `selectedTeamData` | `teamSlice` | Полные данные только одной команды |
| `searchQuery` | `conversationSlice` | Поиск общий для всех вкладок |
| `searchVisible` | `conversationSlice` | Показ поиска общий |
| `searchMatches` | `conversationSlice` | Результаты поиска общие |
| `currentSearchIndex` | `conversationSlice` | Навигация по результатам общая |
| `expandedAIGroupIds` | `conversationSlice` | Legacy дубль `tabUISlice` |
| `expandedDisplayItemIds` | `conversationSlice` | Legacy дубль `tabUISlice` |
| `expandedStepIds` | `conversationSlice` | Глобальное, логично per-tab |
| `activeDetailItem` | `conversationSlice` | Глобальное, логично per-tab |
### ⚠️ Синхронизируемое (работает через swap)
| Состояние | Механизм |
|-----------|----------|
| `selectedProjectId` | Swap при фокусе pane |
| `selectedSessionId` | Swap при фокусе pane |
| `sessionDetail` (global) | Swap из `tabSessionData[tabId]` |
| `conversation` (global) | Swap из `tabSessionData[tabId]` |
---
## Варианты реализации
### Вариант A: Полная поддержка split-screen для сессий
**Надёжность: 8/10 | Уверенность: 9/10**
Основа уже заложена через `tabSessionData`. Нужно:
1. **Search isolation** (~5 файлов):
- Перенести `searchQuery`, `searchVisible`, `searchMatches`, `currentSearchIndex` в `tabUISlice`
- Обновить `SearchBar`, `useSearchContextNavigation`, `searchHighlightUtils`
- Компоненты читают search state через `tabUIStates[tabId]`
2. **Legacy cleanup** (~3 файла):
- Удалить `expandedAIGroupIds` и `expandedDisplayItemIds` из `conversationSlice`
- Убедиться все компоненты используют `tabUISlice` версии
- Удалить `expandedStepIds` из global scope
3. **Верификация** (~3 файла):
- Проверить все компоненты в chat/ читают через `tabSessionData[tabId]` паттерн
- Проверить что `activeDetailItem` изолирован
**Объём: ~8-12 файлов, средняя сложность.**
### Вариант B: Полная поддержка split-screen для команд
**Надёжность: 7/10 | Уверенность: 7/10**
Нужна новая инфраструктура:
1. **Per-tab team data cache** (~5 файлов):
```typescript
// В teamSlice или sessionDetailSlice
tabTeamData: Record<string, {
teamName: string;
teamData: TeamData | null;
loading: boolean;
error: string | null;
}>
```
2. **selectTeam() с tabId** (~3 файла):
- `selectTeam(teamName, tabId?)` — кэширует в `tabTeamData[tabId]`
- При переключении tab: swap из кэша или fetch
- При закрытии tab: cleanup кэша
3. **Team компоненты** (~8 файлов):
- `TeamDetailView`, `TeamChatView`, `TeamKanbanView` и др.
- Читать через `tabTeamData[tabId]` паттерн
- File watcher: обновлять нужные tab кэши
4. **Sidebar sync** (~2 файла):
- При фокусе pane с team tab: sync sidebar к этой команде
**Объём: ~15-20 файлов, высокая сложность.**
### Вариант C: A + B (полный split-screen)
**Надёжность: 6/10 | Уверенность: 7/10**
**Объём: ~20-25 файлов.**
---
## Риски
### Высокие
1. **Race conditions при file watcher events** — обновление прилетает, нужно обновить правильный tab cache. Для сессий решено через `tabFetchGeneration` Map, для команд нужен аналог.
2. **Search isolation** — search завязан на глобальные `searchMatches` и навигацию по ним, самый трудоёмкий рефактор.
### Средние
3. **Memory pressure** — каждый tab хранит полный кэш. Для сессий работает (cleanup при закрытии). Для команд нужен аналог.
4. **Sidebar sync** — сайдбар показывает контекст focused pane. При переключении нужен корректный swap project/worktree/team.
5. **Stale data** — два tab с одной сессией/командой: file watcher обновляет оба или только active?
### Низкие
6. **DnD between panes** — перетаскивание team tab между panes должно триггерить cache transfer.
7. **Tab duplication**`openTab()` проверяет дупликаты across ALL panes. Нужно ли разрешить одну и ту же команду в двух panes?
---
## Ключевые файлы
### Store Slices
| Файл | Роль |
|------|------|
| `src/renderer/store/slices/tabSlice.ts` | Tab lifecycle, session switching, backward compat |
| `src/renderer/store/slices/paneSlice.ts` | Multi-pane split/resize/focus |
| `src/renderer/store/slices/tabUISlice.ts` | Per-tab UI state (expansion, scroll) |
| `src/renderer/store/slices/sessionDetailSlice.ts` | Session data + per-tab caching |
| `src/renderer/store/slices/conversationSlice.ts` | Search, legacy expansion (нужен рефактор) |
| `src/renderer/store/slices/teamSlice.ts` | Team selection (глобальное, нужен рефактор) |
### Layout Components
| Файл | Роль |
|------|------|
| `src/renderer/components/layout/TabbedLayout.tsx` | Main layout + DnD context |
| `src/renderer/components/layout/TabBarRow.tsx` | Full-width tab bar (pane-proportional) |
| `src/renderer/components/layout/TabBar.tsx` | Single pane tab bar |
| `src/renderer/components/layout/PaneContainer.tsx` | Split layout renderer |
| `src/renderer/components/layout/PaneView.tsx` | Single pane wrapper |
| `src/renderer/components/layout/PaneContent.tsx` | Tab content renderer (display-toggle) |
| `src/renderer/components/layout/SessionTabContent.tsx` | Session tab content |
### Contexts
| Файл | Роль |
|------|------|
| `src/renderer/contexts/TabUIContext.tsx` | Per-tab ID provider |
| `src/renderer/contexts/useTabUIContext.ts` | Context hook |
---
## Рекомендация
**Начать с Варианта A** (сессии в split-screen):
- 80% инфраструктуры уже есть
- Нужно дочистить search isolation и legacy duplicates
- Низкий риск регрессий
**Затем Вариант B** (команды):
- Когда паттерн per-tab caching отработан на сессиях
- Применить тот же подход к team data
---
## Обнаруженные баги (побочный результат ресёрча)
1. **Search state не изолирован** — поиск в одной вкладке влияет на другие
2. **Legacy дублирование**`expandedAIGroupIds` существует и в `conversationSlice` и в `tabUISlice`
3. **Team tabs в split pane**обе панели показывают одну команду (последнюю выбранную)