13 KiB
13 KiB
Итерация 03 — Kanban Board (click-to-move) + kanban-state.json
Эта итерация добавляет 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.statusREVIEW,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: stringreviewers: string[](пока только хранение/чтение; управление — итерация 04)tasks: Record<string, KanbanTaskState>
KanbanTaskState (MVP):
column: 'review' | 'approved'reviewStatus?: 'pending' | 'error'reviewer?: string | nullerrorDescription?: stringmovedAt: string(ISO)
Примечание по scope:
- В итерации 03 мы пишем только
reviewStatus: 'pending'при переходе вREVIEW. - Поля
reviewerиerrorDescriptionзакладываем в формат, но не обязаны полноценно использовать до итерации 04.
Column mapping (единый источник правды)
Логика маппинга колонки для задачи:
- Если
kanbanState.tasks[task.id]есть → используемcolumnиз state (review|approved) - Иначе:
pending→todoin_progress→in_progresscompleted→donedeleted→ не показываем
Полностью соответствует docs/team-management/kanban-design.md.
Shared types (src/shared/types/team.ts)
Добавляем типы:
KanbanColumnId = 'todo' | 'in_progress' | 'done' | 'review' | 'approved'KanbanTaskStateKanbanState
И расширяем 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.tssrc/main/services/team/atomicWrite.ts(минимум дляkanban-state.json, расширим в итерации 04 под inbox/task write)src/renderer/components/team/kanban/KanbanBoard.tsxsrc/renderer/components/team/kanban/KanbanColumn.tsxsrc/renderer/components/team/kanban/KanbanTaskCard.tsxsrc/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(bridgeteams.updateKanban)src/main/ipc/guards.ts(+validateTaskId())src/main/ipc/teams.ts(+ handlerTEAM_UPDATE_KANBAN)src/main/services/team/TeamDataService.ts(подключить kanban manager, GC)src/main/services/team/index.ts(exportTeamKanbanManager)src/main/services/infrastructure/FileWatcher.ts(teamsWatcher: учитыватьkanban-state.jsonкакconfigchange)src/renderer/store/slices/teamSlice.ts(actionupdateKanban, refresh после write)src/renderer/components/team/TeamDetailView.tsx(вставить kanban panel)src/renderer/api/httpClient.ts(browser-mode заглушка дляupdateKanban)
Порядок работ (runbook) с контрольными точками
CP0 — типы компилируются
- Shared types
- В
src/shared/types/team.tsдобавить kanban-типы иTeamData.kanbanState. - В
src/shared/types/api.tsдобавитьTeamsAPI.updateKanban.
- В
pnpm typecheck
CP1 — backend читает/пишет kanban-state.json
-
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
-
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 = nowpatch: { remove: true }→ delete entry- write через
atomicWriteAsync
garbageCollect(teamName, validTaskIds):- удаляет записи
tasks[id]которых нет вvalidTaskIds - dirty-check: писать файл только если реально что-то удалили
- удаляет записи
-
TeamDataService
- добавляем зависимость
TeamKanbanManager - в
getTeamData(teamName):- сначала грузим
tasks - затем
kanbanState - затем GC (по
Set(tasks.map(id))) - возвращаем
TeamDataсkanbanState
- сначала грузим
- добавляем зависимость
-
pnpm typecheck
CP2 — IPC: updateKanban работает end-to-end
-
Guards
validateTaskId()вsrc/main/ipc/guards.ts(pattern какvalidateTeamName()):- строка, только цифры, разумный лимит длины (например 1–10)
-
IPC + preload
TEAM_UPDATE_KANBANвsrc/preload/constants/ipcChannels.tssrc/main/ipc/teams.ts: handlerupdateKanban(teamName, taskId, patch):- validateTeamName + validateTaskId
- вызвать
teamDataService.updateKanban(...)(новый метод) - вернуть
IpcResult<void>
src/preload/index.ts:teams.updateKanban(...)черезinvokeIpcWithResult<void>()
-
Быстрая проверка:
- в DevTools:
await window.electronAPI.teams.updateKanban(teamName, taskId, { column: 'review' })
- в DevTools:
-
pnpm typecheck
CP3 — UI: KanbanBoard виден и управляем
- UI компоненты
KanbanBoardполучает:tasks: TeamTask[]kanbanState: KanbanStateonUpdate(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”)
- TeamDetailView layout
- Фиксируем layout без вариантов (чтобы не расползлось):
- Left:
MemberList - Center:
KanbanBoard - Right: текущий
TaskList(из итерации 02) как “сырой список” задач
- Left:
- Обязательные состояния: loading/error/empty/data.
- UI инвариант: kanban всегда строится из
TeamData.tasks+TeamData.kanbanState(никаких дополнительных fetch).
- Live refresh
FileWatcher:- в
teamsWatcherдобавить обработкуkanban-state.jsonкакtype: 'config'(detail:kanban-state.json) - это гарантирует, что существующий renderer coalesce обновит team data
- в
-
pnpm test -
Ручная проверка
- Открыть 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— быть безопасным при частых вызовах.