Files
dc-observatio/docs/tasks/TASK_tgu-editor-integration.md
T
Дмитрий Соловьев f084775cbb feat(tgu-ops-ui): add spacecraft work editor tab
Port the TGU editor prototype into pcp-tgu-ops-ui as a new editor tab, add draft/conflict/pass logic with tests, extend the 2D map picking API, and include editor styles/theme aliases and task/prototype docs.

Validation: npm run test; npm run build in services/pcp-tgu-ops-ui.
2026-05-30 15:28:34 +03:00

220 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Задача: встроить интерфейс «Редактор включений КА» в `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.