# Задача: встроить интерфейс «Редактор включений КА» в `pcp-tgu-ops-ui` > Документ-ТЗ для кодинг-агента. Раздел 0 — промпт для запуска (скопировать в агента). > Разделы 1–11 — инструкция, которую агент читает в репозитории и которой следует. > Стиль работы, границы слоёв и формат финального ответа — по корневому `AGENTS.md`. --- ## 0. Промпт для запуска агента (copy-paste) ``` Ты работаешь в монорепозитории observatio-terrae (pcp). Задача: встроить прототип интерфейса «Редактор включений КА» в существующее фронтенд-приложение services/pcp-tgu-ops-ui (Vite + React 19 + TypeScript). Перед началом обязательно прочитай: 1. /AGENTS.md — правила работы, стиль, формат финального ответа. 2. /docs/tasks/TASK_tgu-editor-integration.md — полное ТЗ этой задачи (этот файл). 3. Исходный прототип: /docs/ui-prototypes/pcp-tgu-editor/ (editor.jsx и соседние). 4. Целевую структуру: services/pcp-tgu-ops-ui/src (App.tsx, features/, api/, styles/). Это ПОРТ, а не копирование: прототип на Babel-standalone с глобалями и мок-данными; цель — ESM/TS-модули, реальный API-клиент, существующая тема и паттерны проекта. Выполняй строго по этапам из раздела 5 ТЗ. Делай минимальные ревьюопригодные изменения, не трогай чужие сервисы и backend (Kotlin). Карту, орбитальную математику и мок-данные из прототипа НЕ переноси — переиспользуй features/tgu-map-2d и api/tguApi (раздел 3). После каждого этапа прогоняй `npm run test` и `npm run build` в модуле. В конце дай отчёт в формате из раздела «Обязательный формат финального ответа» AGENTS.md. ``` --- ## 1. Контекст и цель `pcp-tgu-ops-ui` — операторский UI ТГУ (Mission Ops): уже работающее SPA на **Vite 7 + React 19 + TypeScript**, feature-sliced структура (`features/tgu-planning`, `features/tgu-map-2d`), централизованный API-клиент `src/api/tguApi.ts` (все вызовы через `/api/...`, dev-proxy на backend `:7008`), токены темы в `src/styles/theme.css`, юнит-тесты на Vitest. Прототип «Редактор» — браузерный demo (React/Babel из CDN, JSX компилируется в браузере, общие глобали `D`/`SENSOR_MODES`/`window.WorldGeo`, мок-данные из `data.jsx`). Это **другой стек**, поэтому его нельзя «подключить» файлами — нужно портировать как новую фичу приложения. **Цель:** редактор включений КА доступен как третья вкладка приложения (`timeline | map | editor`), работает на реальных данных через `tguApi`, переиспользует существующую карту и тему, покрыт тестами и собирается без ошибок. Функциональность редактора (из прототипа `editor.jsx`): выбор активного КА/плана, черновик включений (`works`: `shoot` — съёмка, `downlink` — сброс), drag/resize по таймлайну, добавление съёмки по точке на карте и сброса по сеансу со станцией, контекст соседних КА, undo/redo, проверка конфликтов (пересечения по времени — живые; видимость цели/станции — по кнопке), применение правок. --- ## 2. Границы задачи (scope) **В scope:** - Новая фича `src/features/tgu-editor/` в `pcp-tgu-ops-ui`. - Третья вкладка `"editor"` в существующем переключателе вкладок. - Перенос UI-компонентов редактора на TSX + типизация. - Чистые модули логики (undo/redo, конфликты) + их тесты. - Подключение к реальным данным через `tguApi` (планы/платформы). - Перемаппинг токенов темы. **Вне scope (НЕ делать без отдельного запроса):** - Изменения в любых Kotlin-сервисах, включая `pcp-tgu-service` (backend-эндпоинт сохранения правок — отдельная задача, см. раздел 7). - Перенос карты/орбитальной математики/мок-данных из прототипа (раздел 3). - Добавление роутера, state-менеджеров, UI-библиотек, новых рантайм-зависимостей без необходимости. - Рефакторинг существующих фич `tgu-planning` / `tgu-map-2d` сверх минимально необходимого для переиспользования. - Изменения `helm/`, `settings.gradle.kts`, CI (модуль фронта в них и так не участвует). --- ## 3. Целевая архитектура и правила переиспользования Следуй существующему feature-sliced паттерну (см. `features/tgu-planning`, `features/tgu-map-2d`): UI-компоненты в `*.tsx`, типы в `model/*.ts`, нетривиальная логика — в отдельных чистых `*.ts` модулях с тестами. **Обязательно переиспользовать (НЕ дублировать):** - **Карта** — `features/tgu-map-2d` (`Tgu2DMapView`, `canvas/drawTracks|drawSwath|drawStations|drawPlanWorks|drawBaseMap`, `geometry/mapProjection|spatialIndex|bbox`). Редактору нужна карта в режиме «выбор точки съёмки» и подсветки трасс — расширяй существующий компонент пропсами (`pickMode`, `onPickPoint`, `targets`), а не переноси `mapview.jsx`/`orbit.jsx`/`world.js`/`basemap.js`. - **API** — `src/api/tguApi.ts` (`fetchPlans`, `fetchPlatforms`, паттерн `fetchJson`/`TguApiError`). Все сетевые вызовы редактора добавляй сюда же или в `features/tgu-editor/editorApi.ts` в том же стиле. - **Тема** — `src/styles/theme.css` (см. раздел 6). - **Модели** — `src/model/tguTypes.ts`, `src/model/timelineTypes.ts`. Переиспользуй существующие типы планов/платформ, не вводи параллельные. **Не переносить из прототипа:** `mapview.jsx`, `orbit.jsx`, `world.js`, `basemap.js`, `util.jsx` (дублируют то, что уже есть в TS), `data.jsx` (мок), `tabs.jsx` (заменяется вкладкой), `Editor.html`/`Map.html` (точка входа уже `index.html` + `main.tsx`). Орбитальные функции, которых нет в `tgu-map-2d` (`targetPasses`, `stationPasses`, `subPoint`, `nearestStation`), вынеси в чистый общий модуль `features/tgu-map-2d/geometry/passes.ts` (с тестами) и переиспользуй из карты и редактора. Не размещай бизнес/гео-логику внутри React-компонентов. --- ## 4. Инвентаризация и маппинг файлов Источник: `docs/ui-prototypes/pcp-tgu-editor/` (положить туда содержимое архива до начала работ — по образцу уже существующих `docs/ui-prototypes/pcp-tgu-mission-ops/`). | Прототип | Действие | Целевой файл | |---|---|---| | `editor.jsx` (`EditorApp`) | Портировать, убрать `ReactDOM.createRoot`, сделать экспортируемым компонентом-вкладкой | `features/tgu-editor/TguEditorTab.tsx` | | `editor.jsx` (`EditorToolbar`) | Портировать | `features/tgu-editor/EditorToolbar.tsx` | | `editorrail.jsx` | Портировать | `features/tgu-editor/EditorRail.tsx` | | `editortimeline.jsx` | Портировать | `features/tgu-editor/EditorTimeline.tsx` | | `editorpanels.jsx` (`WorkInspector`/`ShootPanel`/`DownlinkPanel`) | Портировать | `features/tgu-editor/EditorPanels.tsx` | | `visibility.jsx` | Портировать визуализацию; гео-расчёты вынести в `passes.ts` | `features/tgu-editor/VisibilityTracks.tsx` | | `editor.jsx` (undo/redo, mutate, seedDraft) | Вынести в чистый модуль | `features/tgu-editor/editorDraft.ts` (+`editorDraft.test.ts`) | | `editor.jsx` (overlaps + runCheck) | Вынести в чистый модуль | `features/tgu-editor/editorConflicts.ts` (+`editorConflicts.test.ts`) | | типы `work`/`draft`/`target` | Создать | `features/tgu-editor/model/editorTypes.ts` | | `mapview.jsx`, `orbit.jsx`, `world.js`, `basemap.js`, `util.jsx`, `data.jsx`, `tabs.jsx`, `*.html` | НЕ переносить | — | --- ## 5. Пошаговый план (атомарные этапы) Каждый этап завершай прогоном `npm run test` и `npm run build` в `services/pcp-tgu-ops-ui`. **Этап 0 — подготовка.** Скопировать прототип в `docs/ui-prototypes/pcp-tgu-editor/`. Прочитать `App.tsx`, `TguPlanningPage.tsx`, `TguToolbar.tsx`, `tguApi.ts`, `theme.css`, `Tgu2DMapView.tsx`. Зафиксировать в отчёте точки расширения. **Этап 1 — типы и каркас фичи.** Создать `features/tgu-editor/model/editorTypes.ts` (`Work` с `kind: "shoot" | "downlink"`, `origin`, `modeId`, `stationId`, `targetLat/Lon`, `isNew`; `Draft`, `EditorTarget`). Создать пустой `TguEditorTab.tsx` с заголовком-заглушкой. Скомпилироваться. **Этап 2 — чистая логика + тесты.** Перенести в `editorDraft.ts`: `seedDraft`, `mutate`, `undo`, `redo`, `addShoot`, `addDownlink`, `updateWork`, `deleteWork`, `duplicateWork` (как чистые функции над `Draft`/`Work`, без React и без `Date.now()` внутри — id/время передавать параметром для детерминизма тестов). Перенести в `editorConflicts.ts`: `overlaps` и проверку видимости. Написать `editorDraft.test.ts` и `editorConflicts.test.ts` (undo/redo инварианты, обнаружение пересечений, отсутствие/наличие видимости). Это первоочередное — логика должна быть покрыта до UI. **Этап 3 — токены темы.** Реализовать перемаппинг (раздел 6). Убедиться, что портируемые стили резолвятся в палитру приложения. **Этап 4 — UI без карты.** Портировать `EditorToolbar`, `EditorRail`, `EditorTimeline`, `EditorPanels`, `VisibilityTracks` на TSX. Завязать их на состояние в `TguEditorTab` (`useState`/`useMemo`/`useCallback` напрямую из `react`). Drag/resize таймлайна сохранить. Вместо `alert()` использовать уведомление в стиле `decisionNotice`/`decisionError`. **Этап 5 — карта.** Расширить `Tgu2DMapView` пропсами `pickMode`/`onPickPoint`/`targets` (минимально, без поломки текущего использования в `Tgu2DMapTab`). Подключить в редакторе для выбора точки съёмки и подсветки трасс цели/станции через `passes.ts`. **Этап 6 — данные.** Заменить мок `D` на реальные данные: планы/платформы грузить через `tguApi` (как в `TguPlanningPage`). `seedDraft` строить из выбранного плана. Активный КА/план брать из пропсов (раздел 7), не из `location.search`. **Этап 7 — вкладка.** В `model/timelineTypes.ts`/`TguPlanningPage.tsx` расширить `activeTab` до `"timeline" | "map" | "editor"`, добавить кнопку вкладки в `TguToolbar` (`onTabChange`), отрендерить `TguEditorTab` при `activeTab === "editor"`, передав `selectedSpacecraftId`/`selectedPlan`/`appliedRange`. **Этап 8 — apply (заглушка).** Реализовать `applyDraft` через типизированную функцию `saveEditedPlan` в `editorApi.ts`, помеченную `// TODO(backend): эндпоинт сохранения правок плана отсутствует в pcp-tgu-service` (раздел 7). Поведение: показать понятное уведомление о том, что сохранение во внешний сервис ещё не реализовано; не падать. **Этап 9 — финализация.** Прогнать `npm run test` и `npm run build`. Отчёт по формату AGENTS.md. --- ## 6. Перемаппинг токенов темы Прототип использует oklch-токены (`--bg-0..3`, `--ink..--ink-3`, `--t-optical/radar/combo`, `--now`, `--warn`, `--line`, `--shadow`), которых нет в `theme.css` приложения (там hex: `--bg-base`, `--bg-panel`, `--text`, `--accent`, `--danger`, `--warning`, ...). Минимально-инвазивный подход: создать `src/styles/editor-tokens.css` (импортировать в `TguEditorTab` или в `main.tsx`) с алиасами, чтобы портированный код, ссылающийся на старые имена, резолвился в палитру приложения — без переписывания каждого inline-стиля: ```css :root { --bg-0: var(--bg-base); --bg-1: var(--bg-panel); --bg-2: var(--bg-panel-2); --bg-3: var(--bg-elevated); --ink: var(--text); --ink-1: var(--text); --ink-2: var(--text-muted); --ink-3: var(--text-dim); --now: var(--warning); --warn: var(--warning); /* --line, --line-soft, --accent, --shadow совпадают по имени — проверить значения */ /* --t-optical/radar/combo: задать на основе --accent/--warning/--danger или вынести в палитру типов КА */ } ``` Уточнить фактические значения `--line/--line-soft/--accent/--shadow` (имена совпадают, значения могут отличаться) и зафиксировать решение в отчёте. Альтернатива (если команда предпочитает) — переписать стили редактора сразу на родные токены приложения; выбрать один путь и не смешивать. --- ## 7. Данные и API - **Чтение:** планы и платформы — через существующие `fetchPlans`/`fetchPlatforms` (`tguApi.ts`). Активный КА/план редактор получает пропсами от `TguPlanningPage` (`selectedSpacecraftId`, `selectedPlan`), а не из URL. - **Гео/орбиты:** через `tgu-map-2d` + `passes.ts`; мок `world.js`/`data.jsx` не использовать. - **Сохранение правок (apply):** в текущем API `pcp-tgu-service` **нет** эндпоинта сохранения отредактированного набора включений (есть только выдача плана и `decision`/`issue`). Поэтому в этой задаче `applyDraft` реализуется как заглушка с явным `TODO(backend)` и не делает реальной записи. **Предложение контракта для backend-команды** (вне scope, зафиксировать в отчёте, не реализовывать): - `POST /api/tgu-planning/plans/{planId}/edits` с телом `{ works: EditedWork[], baseRevision?: string }`, идемпотентно по `operationId`, возвращает новый план/attempt. Семантику (новый attempt vs патч существующего) согласовать отдельно. --- ## 8. Конвенции Следуй `AGENTS.md` (раздел про стиль и scope) применительно к TS/React: - Минимальные, локальные, ревьюопригодные изменения; не переименовывать и не форматировать несвязанные файлы. - Предпочитать `const`, маленькие функции, явные имена; не вводить «умные» абстракции на будущее. - Бизнес/гео-логику не держать в React-компонентах — выносить в чистые `*.ts` модули. - Комментарии — там, где помогают сопровождению (undo/redo инварианты, правила конфликтов, edge cases), а не на каждую строку. Язык комментариев — как в проекте. - Не глотать ошибки сети молча — использовать паттерн `TguApiError`/уведомлений, как в существующем коде. - Не добавлять новые рантайм-зависимости без явной необходимости; React/ReactDOM/Babel из CDN прототипа не тащить — они уже есть в проекте как npm-пакеты. - TypeScript: без `any` в публичных сигнатурах; типы — в `model/`. --- ## 9. Критерии приёмки и команды валидации Рабочая директория: `services/pcp-tgu-ops-ui`. ``` npm install # при необходимости npm run test # vitest run — все тесты зелёные, включая новые editorDraft/editorConflicts/passes npm run build # tsc -b && vite build — без ошибок типов и сборки npm run dev # ручная проверка: вкладка «Редактор» открывается, см. ниже ``` Функциональные критерии: 1. В тулбаре доступна третья вкладка; переключение `timeline | map | editor` работает, существующие вкладки не сломаны. 2. Редактор открывается на реальных данных (планы/платформы из `tguApi`), без мок-`D`. 3. Работают: drag/resize включений, добавление съёмки по точке на карте, добавление сброса по станции, undo/redo, проверка конфликтов (живые пересечения + видимость по кнопке). 4. Карта в редакторе — это `tgu-map-2d` (нет второго движка карты в бандле). 5. Тема консистентна с приложением (нет «бесцветных» из-за отсутствующих токенов). 6. `applyDraft` не падает и явно сообщает про отсутствующий backend-эндпоинт. Если какая-то команда не запускалась — указать какая и почему, и описать остаточный риск (по AGENTS.md). --- ## 10. Риски, допущения, чего не делать - **Допущение:** структура планов/включений из `tguApi` достаточна, чтобы построить `seedDraft`. Если в реальном ответе нет полей под `works` редактора — зафиксировать gap, сделать адаптер-маппер и описать недостающие поля в отчёте, **не** менять backend. - **Риск:** React 19 + StrictMode даёт двойной вызов эффектов — проверить `runCheck`/`setTimeout` на отсутствие двойных срабатываний. - **Риск:** расширение `Tgu2DMapView` может задеть `Tgu2DMapTab` — новые пропсы делать опциональными с дефолтами, существующее поведение не менять. - **Не делать:** правки в Kotlin-сервисах, в чужих фичах сверх необходимого, новый роутинг, новые тяжёлые зависимости, перенос мок-данных и второго движка карты. --- ## 11. Обязательный формат финального ответа По `AGENTS.md` агент завершает задачу блоками: 1. **Changed files** — список изменённых/созданных файлов. 2. **What changed** — краткое summary реализации по этапам. 3. **Architecture compliance** — почему соблюдены границы слоёв (логика вне компонентов, переиспользование `tgu-map-2d`/`tguApi`/темы, отсутствие дублей карты и мок-данных). 4. **Validation** — какие команды (`npm run test`, `npm run build`, ручная проверка) запускались и результаты. 5. **Risks / assumptions** — что не проверено, gap по данным/backend, edge cases.