agent-ecosystem/docs/iterations/iteration-03-kanban-board.md
2026-02-21 09:47:24 +02:00

13 KiB
Raw Blame History

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