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.
This commit is contained in:
Дмитрий Соловьев
2026-05-30 15:28:34 +03:00
parent c3a1a8b4a1
commit f084775cbb
58 changed files with 6609 additions and 7 deletions
+219
View File
@@ -0,0 +1,219 @@
# Задача: встроить интерфейс «Редактор включений КА» в `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.