agent-ecosystem/docs/iterations/iteration-03-kanban-board.md

270 lines
13 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.

# Итерация 03 — Kanban Board (click-to-move) + `kanban-state.json`
> Historical note
> This document captures the planned Kanban scope at iteration time.
> It is not the source of truth for the current product contract.
> For the current review flow, see [../team-management/README.md](../team-management/README.md) and [../team-management/kanban-design.md](../team-management/kanban-design.md).
Эта итерация добавляет **kanban-доску команды** во вкладке Team и вводит **персистентное состояние** для колонок `REVIEW`/`APPROVED` через файл `~/.claude/teams/{teamName}/kanban-state.json`.
Основание:
- `docs/team-management/implementation.md` (**Iteration 3: Kanban Board**)
- `docs/team-management/kanban-design.md` (колонки, storage, mapping, GC)
- `docs/team-management/research-tasks.md` (ограничения task-файлов, почему не metadata)
---
## Цель итерации
- Во вкладке Team появляется **Kanban** с 5 колонками:
- `TODO`, `IN PROGRESS`, `DONE`**авто-маппинг** из `task.status`
- `REVIEW`, `APPROVED` — управляются **только** через `kanban-state.json`
- Поддерживаем MVP UX: **click-to-move** (select/dropdown на карточке).
- Перемещения пишутся в `~/.claude/teams/{teamName}/kanban-state.json` (atomic write).
- Live refresh уже существующий (`team:change`) обновляет kanban при изменениях tasks/teams.
---
## Не-цели (строго вне scope)
- Drag-and-drop (Phase 2, `@dnd-kit`).
- Отправка сообщений ревьюверу, MessageComposer, ActivityTimeline (это итерация 04).
- Изменение `task.status` (Fix flow) и любые write-path в `~/.claude/tasks/**` (это итерация 04).
- `reviewHistory`, round-robin балансировка ревьюверов (Phase 2).
- Создание новых задач (из-за `.highwatermark` риска, см. `research-tasks.md`).
---
## Контракт итерации (Main → Preload → Renderer)
### Storage: путь и формат
- **Файл**: `~/.claude/teams/{teamName}/kanban-state.json`
- Причина: task `metadata` может быть перезаписан агентом (см. `research-tasks.md`).
Минимальный формат (под MVP):
- `teamName: string`
- `reviewers: string[]` (пока только хранение/чтение; управление — итерация 04)
- `tasks: Record<string, KanbanTaskState>`
`KanbanTaskState` (MVP):
- `column: 'review' | 'approved'`
- `reviewStatus?: 'pending' | 'error'`
- `reviewer?: string | null`
- `errorDescription?: string`
- `movedAt: string` (ISO)
Примечание по scope:
- В итерации 03 мы **пишем только** `reviewStatus: 'pending'` при переходе в `REVIEW`.
- Поля `reviewer` и `errorDescription` закладываем в формат, но не обязаны полноценно использовать до итерации 04.
### Column mapping (единый источник правды)
Логика маппинга колонки для задачи:
1) Если `kanbanState.tasks[task.id]` есть → используем `column` из state (`review|approved`)
2) Иначе:
- `pending``todo`
- `in_progress``in_progress`
- `completed``done`
- `deleted` → не показываем
Полностью соответствует `docs/team-management/kanban-design.md`.
### Shared types (`src/shared/types/team.ts`)
Добавляем типы:
- `KanbanColumnId = 'todo' | 'in_progress' | 'done' | 'review' | 'approved'`
- `KanbanTaskState`
- `KanbanState`
И расширяем `TeamData`:
- `kanbanState: KanbanState`
### IPC / TeamsAPI
Добавляем канал и метод для обновления kanban state:
- `TEAM_UPDATE_KANBAN = 'team:updateKanban'`
- `TeamsAPI.updateKanban(teamName: string, taskId: string, patch: UpdateKanbanPatch): Promise<void>`
`UpdateKanbanPatch` (строго фиксируем, без “и так понятно”):
- `{ op: 'set_column'; column: 'review' | 'approved' }`
- `{ op: 'remove' }`
Поведение:
- `set_column: review` → записываем `{ column:'review', reviewStatus:'pending', reviewer:null, movedAt: now }`
- `set_column: approved` → записываем `{ column:'approved', movedAt: now }` (reviewStatus/reviewer можно очистить)
- `remove` → удаляем запись для `taskId` из `kanbanState.tasks`
Требования:
- Валидация `teamName` (уже есть).
- Валидация `taskId` (добавляем `validateTaskId()`).
- IPC возвращает `IpcResult<void>` (единый паттерн проекта).
- Browser-mode: метод должен возвращать понятную ошибку “доступно только в Electron”, как и остальной Teams API.
---
## Definition of Done (DoD)
- **UI**
- В Team tab виден kanban с 5 колонками.
- Карточки задач появляются в корректных колонках по mapping-логике.
- На карточке в `DONE` есть `Move to: REVIEW` (click-to-move).
- На карточке в `REVIEW` есть `Move to: APPROVED` и `Move back` (remove state → вернётся в `DONE` по status).
- В `REVIEW` показывается `ReviewBadge` со статусом `Pending` (manual review; без авто-назначения в итерации 03).
- Пустые состояния: “нет задач”, “не удалось загрузить kanban”.
- **Backend/IPC**
- `TEAM_GET_DATA` возвращает `TeamData` с `kanbanState`.
- `TEAM_UPDATE_KANBAN` обновляет `kanban-state.json` атомарно.
- GC kanban-state выполняется **только после** полной загрузки `tasks` (см. `kanban-design.md`).
- **Live refresh**
- Изменение `kanban-state.json` приводит к refresh team data (через существующий `team:change` → store).
- **Качество**
- `pnpm typecheck` проходит.
- `pnpm test` проходит.
---
## Выходные изменения (файлы)
### Новые файлы
- `src/main/services/team/TeamKanbanManager.ts`
- `src/main/services/team/atomicWrite.ts` (минимум для `kanban-state.json`, расширим в итерации 04 под inbox/task write)
- `src/renderer/components/team/kanban/KanbanBoard.tsx`
- `src/renderer/components/team/kanban/KanbanColumn.tsx`
- `src/renderer/components/team/kanban/KanbanTaskCard.tsx`
- `src/renderer/components/team/kanban/ReviewBadge.tsx`
### Изменяемые файлы
- `src/shared/types/team.ts` (kanban types + `TeamData.kanbanState`)
- `src/shared/types/api.ts` (TeamsAPI.updateKanban)
- `src/preload/constants/ipcChannels.ts` (+ `TEAM_UPDATE_KANBAN`)
- `src/preload/index.ts` (bridge `teams.updateKanban`)
- `src/main/ipc/guards.ts` (+ `validateTaskId()`)
- `src/main/ipc/teams.ts` (+ handler `TEAM_UPDATE_KANBAN`)
- `src/main/services/team/TeamDataService.ts` (подключить kanban manager, GC)
- `src/main/services/team/index.ts` (export `TeamKanbanManager`)
- `src/main/services/infrastructure/FileWatcher.ts` (teamsWatcher: учитывать `kanban-state.json` как `config` change)
- `src/renderer/store/slices/teamSlice.ts` (action `updateKanban`, refresh после write)
- `src/renderer/components/team/TeamDetailView.tsx` (вставить kanban panel)
- `src/renderer/api/httpClient.ts` (browser-mode заглушка для `updateKanban`)
---
## Порядок работ (runbook) с контрольными точками
### CP0 — типы компилируются
1) **Shared types**
- В `src/shared/types/team.ts` добавить kanban-типы и `TeamData.kanbanState`.
- В `src/shared/types/api.ts` добавить `TeamsAPI.updateKanban`.
2) `pnpm typecheck`
---
### CP1 — backend читает/пишет `kanban-state.json`
3) **Atomic write (минимум)**
- `src/main/services/team/atomicWrite.ts`:
- `atomicWriteAsync(targetPath, data)` через `tmp + rename` + `mkdir({ recursive:true })`
- best-effort `fsync` (`open(tmp, 'r+')` → `sync()`), без hard-fail
- EXDEV fallback (copy+unlink) как safety net
4) **TeamKanbanManager**
- `getState(teamName)`:
- читает `~/.claude/teams/{teamName}/kanban-state.json`
- ENOENT → default `{ teamName, reviewers: [], tasks: {} }`
- invalid JSON → default + warning (через `TeamData.warnings`)
- `updateTask(teamName, taskId, patch)`:
- `patch: { column: 'review'|'approved' }` → set state + `movedAt = now`
- `patch: { remove: true }` → delete entry
- write через `atomicWriteAsync`
- `garbageCollect(teamName, validTaskIds)`:
- удаляет записи `tasks[id]` которых нет в `validTaskIds`
- dirty-check: писать файл только если реально что-то удалили
5) **TeamDataService**
- добавляем зависимость `TeamKanbanManager`
- в `getTeamData(teamName)`:
- сначала грузим `tasks`
- затем `kanbanState`
- затем **GC** (по `Set(tasks.map(id))`)
- возвращаем `TeamData` с `kanbanState`
6) `pnpm typecheck`
---
### CP2 — IPC: updateKanban работает end-to-end
7) **Guards**
- `validateTaskId()` в `src/main/ipc/guards.ts` (pattern как `validateTeamName()`):
- строка, только цифры, разумный лимит длины (например 110)
8) **IPC + preload**
- `TEAM_UPDATE_KANBAN` в `src/preload/constants/ipcChannels.ts`
- `src/main/ipc/teams.ts`: handler `updateKanban(teamName, taskId, patch)`:
- validateTeamName + validateTaskId
- вызвать `teamDataService.updateKanban(...)` (новый метод)
- вернуть `IpcResult<void>`
- `src/preload/index.ts`: `teams.updateKanban(...)` через `invokeIpcWithResult<void>()`
9) Быстрая проверка:
- в DevTools: `await window.electronAPI.teams.updateKanban(teamName, taskId, { column: 'review' })`
10) `pnpm typecheck`
---
### CP3 — UI: KanbanBoard виден и управляем
11) **UI компоненты**
- `KanbanBoard` получает:
- `tasks: TeamTask[]`
- `kanbanState: KanbanState`
- `onUpdate(taskId, patch)` → вызывает `teams.updateKanban(...)` через store action
- `KanbanTaskCard`:
- показывает subject / owner / status
- показывает `Move to:` select **только для разрешённых переходов**:
- `DONE → REVIEW` (set column review)
- `REVIEW → APPROVED` (set column approved)
- `REVIEW → DONE` (remove state)
- `APPROVED → DONE` (remove state)
- для `TODO`/`IN PROGRESS` — только read-only (двигать нельзя)
- В колонке `REVIEW` рендерит `ReviewBadge` (в итерации 03 всегда “Pending”)
12) **TeamDetailView layout**
- Фиксируем layout без вариантов (чтобы не расползлось):
- **Left**: `MemberList`
- **Center**: `KanbanBoard`
- **Right**: текущий `TaskList` (из итерации 02) как “сырой список” задач
- Обязательные состояния: loading/error/empty/data.
- UI инвариант: kanban всегда строится из `TeamData.tasks` + `TeamData.kanbanState` (никаких дополнительных fetch).
13) **Live refresh**
- `FileWatcher`:
- в `teamsWatcher` добавить обработку `kanban-state.json` как `type: 'config'` (detail: `kanban-state.json`)
- это гарантирует, что существующий renderer coalesce обновит team data
14) `pnpm test`
15) Ручная проверка
- Открыть Team tab → kanban виден.
- Взять completed задачу → `Move to REVIEW` → появляется в REVIEW и создаётся/обновляется `kanban-state.json`.
- `Move back` → запись удаляется, задача возвращается в DONE.
- Отредактировать `kanban-state.json` вручную → UI обновится.
---
## Риски и митигации
- **GC удалит валидные записи**: запрещено делать GC до чтения tasks. В `TeamDataService` фиксируем порядок (tasks → kanban → GC).
- **EXDEV/rename нюансы**: в atomic write добавляем fallback copy+unlink.
- **Синхронизация UI**: после `updateKanban` делаем `refreshTeamData(teamName)` (и всё равно придёт watcher-событие; refresh должен быть идемпотентен).
- **Шум от fs.watch**: kanban-write может вызвать два refresh (ручной + watcher). Это ок, но store должен coalesce, а `refreshTeamData` — быть безопасным при частых вызовах.