diff --git a/docs_prod/1_STEP_cross-project-task-routing/12_STEP_external-contours-bidirectional-board.md b/docs_prod/1_STEP_cross-project-task-routing/12_STEP_external-contours-bidirectional-board.md index db9d7ef..4ba11d3 100644 --- a/docs_prod/1_STEP_cross-project-task-routing/12_STEP_external-contours-bidirectional-board.md +++ b/docs_prod/1_STEP_cross-project-task-routing/12_STEP_external-contours-bidirectional-board.md @@ -113,6 +113,9 @@ Он нужен как стабильный контракт уровня доски. +Детализация этого контракта вынесена отдельно в: +- [13_STEP_external-contours-board-data-contract.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/13_STEP_external-contours-board-data-contract.md) + ### 3. Сделать board как фиксированные системные представления Первая версия не должна строиться как свободный пользовательский конструктор. @@ -195,3 +198,4 @@ Связанный документ: - [11_STEP_external-contours-detail-shell.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/11_STEP_external-contours-detail-shell.md) +- [13_STEP_external-contours-board-data-contract.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/13_STEP_external-contours-board-data-contract.md) diff --git a/docs_prod/1_STEP_cross-project-task-routing/13_STEP_external-contours-board-data-contract.md b/docs_prod/1_STEP_cross-project-task-routing/13_STEP_external-contours-board-data-contract.md new file mode 100644 index 0000000..c756052 --- /dev/null +++ b/docs_prod/1_STEP_cross-project-task-routing/13_STEP_external-contours-board-data-contract.md @@ -0,0 +1,460 @@ +# Шаг 13. Технический контракт двусторонней доски внешних контуров + +## Зачем нужен отдельный технический шаг + +Продуктовое решение по двусторонней доске уже зафиксировано: +- нужны `Исходящие` +- нужны `Входящие` +- обе зоны должны фильтроваться единообразно +- открытие карточки должно вести в общий shell +- drag-and-drop между зонами не нужен и не должен появиться + +Но текущая реализация проекта не дает собрать это одной UI-переоберткой. + +Нужен отдельный технический контракт уровня board-layer. + +## Что есть в коде сейчас + +### 1. `external-contours` сейчас source-side only + +Текущий endpoint: +- `GET /api/workspaces/:slug/projects/:project_id/external-contours/` + +Сейчас возвращает только записи, где: +- `extra.bridge = external-contours` +- `extra.source_project_id = current project` + +То есть это только исходящие запросы проекта-источника. + +Следствие: +- текущий список не умеет входящие +- текущий detail endpoint тоже source-side only + +### 2. Detail endpoint тоже завязан только на перспективу источника + +Текущий detail: +- `GET /api/workspaces/:slug/projects/:project_id/external-contours/:request_id/` + +Он тоже фильтрует по `extra.source_project_id = current project`. + +Следствие: +- из проекта-цели нельзя открыть ту же сущность через тот же endpoint +- двусторонняя доска не может использовать один и тот же detail flow без нового контракта доступа + +### 3. Frontend store внешних контуров слишком узкий + +Текущий `ProjectExternalContoursStore` умеет: +- список запросов +- вкладки `open / closed` +- create +- update +- source-side actions `accept / decline / reply` + +Но он не умеет: +- фильтры по пользователям, статусам, датам и проектам +- сортировки +- две зоны в одной модели +- отдельные cursors по зонам + +### 4. Входящая сторона живет в другом модуле + +Входящие рабочие объекты сейчас живут в `intake`: +- у них свой store +- свой фильтровый тип +- своя пагинация +- своя модель выдачи + +То есть сегодня `Исходящие` и `Входящие` — это две разные подсистемы. + +### 5. Существующий `views` слой нельзя просто взять как есть + +Слой `ProjectView` уже умеет: +- rich filters +- display filters +- display properties + +Но он привязан к обычным `Issue` layouts проекта. + +Он не является готовой моделью для межконтурной доски, потому что: +- колонка там означает layout/grouping issues +- а в `Внешних контурах` колонка должна означать направление или выборку +- и сама сущность там не сводится к plain `Issue` + +## Технический вывод + +Новая доска должна строиться не вокруг существующего `Issue board`, а вокруг отдельного board contract для сущности `External Contour Request`. + +При этом сам доменный источник правды можно по-прежнему держать на `IntakeIssue + bridge metadata`. + +## Что считаем единицей доски + +Единица двусторонней доски — это не обычный `Issue`. + +Единица доски — это `External Contour Board Item`, то есть проекция bridge-сущности на UI-слой внешнего контура. + +Базовый идентификатор: +- `request_id = IntakeIssue.id` + +Это важно, потому что: +- исходящий и входящий сценарии должны указывать на одну и ту же межконтурную сущность +- detail-shell должен открываться по одному стабильному id + +## Целевой frontend type contract + +```ts +type TExternalContourBoardDirection = "outgoing" | "incoming"; + +type TExternalContourBoardStatus = "open" | "closed"; + +type TExternalContourBoardItem = { + id: string; + direction: TExternalContourBoardDirection; + status: TExternalContourBoardStatus; + has_unread_updates: boolean; + requested_at: string | null; + updated_at: string; + source_decision?: "accepted" | null; + issue: { + id: string; + sequence_id: number | null; + name: string; + description_html?: string | null; + priority?: "low" | "medium" | "high" | "urgent" | "none"; + target_date?: string | null; + state_id?: string | null; + state_detail?: { + id: string; + name: string; + group: string; + color: string; + } | null; + assignee_details?: { + id: string; + display_name: string; + avatar_url?: string | null; + }[]; + created_by_detail?: { + id: string; + display_name: string; + avatar_url?: string | null; + } | null; + label_details?: { + id: string; + name: string; + color: string; + }[]; + }; + source_project: { + id: string; + identifier?: string | null; + name: string; + logo_props?: unknown; + } | null; + target_project: { + id: string; + identifier?: string | null; + name: string; + logo_props?: unknown; + } | null; + requested_by: { + id: string | null; + display_name: string | null; + } | null; + capabilities: { + can_open_detail: boolean; + can_open_target_issue: boolean; + can_edit_request: boolean; + can_reply: boolean; + can_source_decide: boolean; + }; +}; +``` + +## Целевой filter contract + +Это отдельный filter layer для board-level представления, а не копия `inbox` и не копия `project views`. + +```ts +type TExternalContourBoardFilter = { + direction?: TExternalContourBoardDirection[]; + status?: ("open" | "closed")[]; + state_ids?: string[]; + priority?: ("low" | "medium" | "high" | "urgent" | "none")[]; + assignee_ids?: string[]; + created_by_ids?: string[]; + requested_by_ids?: string[]; + source_project_ids?: string[]; + target_project_ids?: string[]; + label_ids?: string[]; + has_unread_updates?: boolean; + created_at?: string[]; + updated_at?: string[]; + target_date?: string[]; + search?: string; +}; + +type TExternalContourBoardSorting = { + order_by?: "requested_at" | "updated_at" | "issue__sequence_id" | "target_date"; + sort_by?: "asc" | "desc"; +}; +``` + +### Почему фильтры именно такие + +- `direction` нужен для будущих пользовательских представлений +- `status` нужен для рабочего деления `open / closed` +- `state_ids`, `assignee_ids`, `created_by_ids`, `label_ids`, `priority` повторяют рабочую логику внутренних контуров +- `source_project_ids` и `target_project_ids` нужны именно для межконтурной аналитики и наблюдения +- `requested_by_ids` нужен отдельно, потому что внешний инициатор и внутренний `created_by` обычной задачи не всегда совпадают по смыслу +- `has_unread_updates` нужен для рабочего мониторинга + +## Целевой backend API + +### 1. Board list endpoint + +Нужен отдельный endpoint уровня доски: + +`GET /api/workspaces/:slug/projects/:project_id/external-contours/board/` + +Он не должен заменять текущий source-side list сразу. + +Он должен стать новым контрактом именно для двусторонней доски. + +### Query params + +```text +direction=outgoing,incoming +status=open,closed +state_ids=, +priority=urgent,high +assignee_ids=, +created_by_ids=, +requested_by_ids=, +source_project_ids=, +target_project_ids=, +label_ids=, +has_unread_updates=true +created_at=, +updated_at=, +target_date=, +search= +order_by=updated_at +sort_by=desc +outgoing_cursor= +incoming_cursor= +per_page=20 +``` + +### Response shape + +```json +{ + "filters": { + "direction": ["outgoing", "incoming"], + "status": ["open"], + "assignee_ids": [], + "source_project_ids": [] + }, + "sorting": { + "order_by": "updated_at", + "sort_by": "desc" + }, + "columns": [ + { + "key": "outgoing", + "title": "Исходящие", + "total_count": 12, + "next_cursor": "abc", + "results": [] + }, + { + "key": "incoming", + "title": "Входящие", + "total_count": 7, + "next_cursor": "def", + "results": [] + } + ] +} +``` + +### Почему один endpoint на обе зоны + +Потому что это дает: +- единый filter contract +- единые count values +- единый sorting contract +- отсутствие расхождений между двумя независимыми загрузками + +При этом пагинация должна быть раздельной: +- отдельный cursor для `outgoing` +- отдельный cursor для `incoming` + +## Целевой detail contract + +Нужен новый detail endpoint с перспективой доступа, а не только source-side read. + +Рекомендуемый вариант: + +`GET /api/workspaces/:slug/projects/:project_id/external-contours/board-items/:request_id/` + +Контракт должен сам определить perspective относительно текущего `project_id`: +- если проект совпадает с `source_project_id` — это `outgoing` +- если проект совпадает с `target_project_id` или с проектом bridge issue — это `incoming` + +Следствие: +- detail-shell открывается по одному route-contract +- входящая зона не должна притворяться source-side проектом только ради открытия карточки + +### Что важно не ломать + +Текущие source-side mutation endpoints можно оставить отдельными: +- update +- decision +- reply + +Но read-model detail для board должен быть общим. + +## Рекомендуемая модель backend aggregation + +### Базовый источник правды + +Базовая сущность не меняется: +- `IntakeIssue` +- `Issue` +- `extra.bridge = external-contours` + +### Агрегация по направлениям + +`outgoing`: +- `extra.source_project_id = current project` + +`incoming`: +- `project_id = current project` +- `extra.bridge = external-contours` + +### Нормализованный serializer + +Нужен отдельный serializer уровня board item. + +Он должен: +- выдавать `direction` +- выдавать обе project references +- отдавать capabilities +- отдавать status и has_unread_updates +- нормализовать issue-поля в один и тот же формат для обеих сторон + +## Frontend store design + +Рекомендуется не расширять бесконечно текущий `ProjectExternalContoursStore`. + +Нужен отдельный store: + +`ProjectExternalContoursBoardStore` + +### Что он хранит + +- board filters +- board sorting +- map items by id +- columns: + - `outgoing` + - `incoming` +- per-column cursor +- per-column total_count +- selected board item id +- board loader states + +### Что остается в текущем store + +Текущий `ProjectExternalContoursStore` можно временно оставить для: +- create request +- source-side update +- source-side decision +- source-side reply +- target project options + +То есть board store отвечает за read-model, а существующий store пока отвечает за mutations. + +Это позволит не ломать уже рабочий runtime одним большим переносом. + +## UI contract + +### 1. Доска состоит из фиксированных системных колонок + +- колонка `Исходящие` +- колонка `Входящие` +- дальше уже поверх них могут появиться пользовательские представления + +### 2. Колонка — это выборка, а не workflow stage + +Следовательно: +- нет drag handle +- нет `onDrop` +- нет optimistic move между колонками + +### 3. Клик по карточке ведет в shell шага 11 + +Детали должны открываться через общий shell, а не через отдельный старый source-only right pane. + +## Что сознательно не делаем на этом шаге + +### 1. Не склеиваем две выдачи в браузере + +Это проигрышно из-за: +- разных сортировок +- разных count values +- разных filter models +- разных правил пагинации + +### 2. Не превращаем board в kanban по статусам + +Это не тот продуктовый сценарий. + +### 3. Не переносим пользовательские колонки в первый технический контракт + +Для начала нужен стабильный системный board layer. + +Пользовательские представления должны появиться только поверх уже работающего fixed contract. + +## Порядок реализации + +### Шаг A. Backend contract + +- board list endpoint +- common board item serializer +- detail read endpoint для обеих перспектив +- filter parsing +- per-column pagination + +### Шаг B. Frontend types and services + +- новые types для board item, filters, sorting, response +- новый service layer +- новый board store + +### Шаг C. UI board shell + +- fixed `Исходящие / Входящие` +- фильтровая пипка +- counts +- открытие detail-shell + +### Шаг D. Cut-over + +- перевод `Внешних контуров` со старого sidebar/list режима на board mode +- сохранение работающих mutation flows +- последующее упрощение legacy source-only списка + +## Критерий приемки технического шага + +- у команды есть конкретный API contract для двусторонней доски +- у команды есть отдельный frontend type contract, не завязанный на plain `Issue` +- у detail-shell есть единый read endpoint для `incoming` и `outgoing` +- документ прямо фиксирует отказ от frontend-склейки и drag-and-drop + +## Связанные документы + +- [11_STEP_external-contours-detail-shell.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/11_STEP_external-contours-detail-shell.md) +- [12_STEP_external-contours-bidirectional-board.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/12_STEP_external-contours-bidirectional-board.md) +- [phase-roadmap.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/phase-roadmap.md) diff --git a/docs_prod/1_STEP_cross-project-task-routing/README.md b/docs_prod/1_STEP_cross-project-task-routing/README.md index 9babdbc..97c98aa 100644 --- a/docs_prod/1_STEP_cross-project-task-routing/README.md +++ b/docs_prod/1_STEP_cross-project-task-routing/README.md @@ -489,3 +489,4 @@ - [phase-roadmap.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/phase-roadmap.md) - [11_STEP_external-contours-detail-shell.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/11_STEP_external-contours-detail-shell.md) - [12_STEP_external-contours-bidirectional-board.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/12_STEP_external-contours-bidirectional-board.md) +- [13_STEP_external-contours-board-data-contract.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/13_STEP_external-contours-board-data-contract.md) diff --git a/docs_prod/1_STEP_cross-project-task-routing/phase-roadmap.md b/docs_prod/1_STEP_cross-project-task-routing/phase-roadmap.md index dce54eb..d9d11e1 100644 --- a/docs_prod/1_STEP_cross-project-task-routing/phase-roadmap.md +++ b/docs_prod/1_STEP_cross-project-task-routing/phase-roadmap.md @@ -41,6 +41,7 @@ Подробные архитектурные шаги вынесены в: - [11_STEP_external-contours-detail-shell.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/11_STEP_external-contours-detail-shell.md) - [12_STEP_external-contours-bidirectional-board.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/12_STEP_external-contours-bidirectional-board.md) +- [13_STEP_external-contours-board-data-contract.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/13_STEP_external-contours-board-data-contract.md) ## Этап 0. Термины и навигация @@ -338,6 +339,7 @@ Подробно описано в: - [12_STEP_external-contours-bidirectional-board.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/12_STEP_external-contours-bidirectional-board.md) +- [13_STEP_external-contours-board-data-contract.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/docs_prod/1_STEP_cross-project-task-routing/13_STEP_external-contours-board-data-contract.md) ## Этап 8. Пользовательские представления поверх двусторонней доски