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

13 KiB
Raw Blame History

Итерация 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.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. Иначе:
    • pendingtodo
    • in_progressin_progress
    • completeddone
    • 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

  1. 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
  2. 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: писать файл только если реально что-то удалили
  3. TeamDataService

    • добавляем зависимость TeamKanbanManager
    • в getTeamData(teamName):
      • сначала грузим tasks
      • затем kanbanState
      • затем GC (по Set(tasks.map(id)))
      • возвращаем TeamData с kanbanState
  4. pnpm typecheck


CP2 — IPC: updateKanban работает end-to-end

  1. Guards

    • validateTaskId() в src/main/ipc/guards.ts (pattern как validateTeamName()):
      • строка, только цифры, разумный лимит длины (например 110)
  2. 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>()
  3. Быстрая проверка:

    • в DevTools: await window.electronAPI.teams.updateKanban(teamName, taskId, { column: 'review' })
  4. pnpm typecheck


CP3 — UI: KanbanBoard виден и управляем

  1. 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”)
  1. TeamDetailView layout
  • Фиксируем layout без вариантов (чтобы не расползлось):
    • Left: MemberList
    • Center: KanbanBoard
    • Right: текущий TaskList (из итерации 02) как “сырой список” задач
  • Обязательные состояния: loading/error/empty/data.
  • UI инвариант: kanban всегда строится из TeamData.tasks + TeamData.kanbanState (никаких дополнительных fetch).
  1. Live refresh
  • FileWatcher:
    • в teamsWatcher добавить обработку kanban-state.json как type: 'config' (detail: kanban-state.json)
    • это гарантирует, что существующий renderer coalesce обновит team data
  1. pnpm test

  2. Ручная проверка

  • Открыть 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 — быть безопасным при частых вызовах.