13 KiB
Итерация 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 and ../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.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— быть безопасным при частых вызовах.