feat(map): add reference layers and stabilize workspace controls

This commit is contained in:
Codex
2026-07-25 13:12:24 +03:00
parent c859ae4db0
commit eaecce9f56
29 changed files with 1593 additions and 247 deletions
+19 -2
View File
@@ -39,7 +39,7 @@ FieldFrame объединяет label, control, hint и description. TextField/T
## RangeControl
Pill-range с заполнением акцентным цветом, встроенной подписью и значением. Домен определяет min/max/step и формат числа.
Pill-range с заполнением акцентным цветом, встроенной подписью и значением. Домен определяет min/max/step и формат числа. Drag всегда принадлежит невидимому native range и продолжается под областью значения. Над числом постоянно смонтирован один прозрачный native text input: клик включает редактирование без замены DOM-узла, браузер ставит каретку в фактическое место клика, а drag-selection остаётся нативным. Поле не получает собственной подложки, select-all или второго focus-ring. Enter/blur применяют значение с clamp/step-нормализацией, Escape отменяет ввод; события редактора не всплывают в оконные shortcuts. Оконный focus-manager выполняет начальный autofocus только при открытии и не отбирает фокус при последующих ререндерах.
## ColorField
@@ -92,7 +92,19 @@ Window — единая механика открытия modal и правой
`WorkspaceWindow` — modeless-окно внутри рабочей сцены приложения. Оно рендерится непосредственно в переданном workspace, не использует portal и не может перекрыть шапку, навигацию или соседние панели за пределами этого workspace.
Приложение контролирует `rect`, `maximized`, видимость, active-state и `zIndex`. Компонент владеет pointer/keyboard-механикой перемещения и изменения размера, кнопками maximize/restore и close, а также повторно ограничивает геометрию при изменении размеров workspace через `ResizeObserver`. Родительский bounds-контейнер должен быть позиционированным и обрезать содержимое (`position: relative; overflow: hidden`).
На Map Page `WorkspaceWindow` используется и для facet-групп, и для
provider-neutral карточки выбранного subject. Chevron внутри facet-строки
владеет только раскрытием дерева subjects; тело строки сохраняет действие
фильтра. Смена совместимого subject не пересоздаёт карточку и не сбрасывает
активную вкладку. Joined data-product aspects не создают отдельные окна или
слои, а наполняют декларативные секции существующей карточки.
Справочные точки транспорта используют тот же renderer adapter, но отдельный
slot `reference-points`. Их три presentation profiles (`Метро`, `Вокзалы`,
`Станции РЖД`) являются живыми каноническими Inspector sections: приложение
может менять высоту, размер, label и LOD, не создавая provider-specific UI.
Приложение контролирует `rect`, `maximized`, видимость, active-state и `zIndex`. Компонент владеет pointer/keyboard-механикой перемещения и изменения размера, кнопками maximize/restore и close, а также повторно ограничивает геометрию при изменении размеров workspace через `ResizeObserver`. Опциональный `autoHeight` подгоняет высоту под живое содержимое и сжимает её обратно, не выходя за нижнюю границу workspace. Родительский bounds-контейнер должен быть позиционированным и обрезать содержимое (`position: relative; overflow: hidden`).
Окно предназначено для вспомогательных камер, инструментов и сопоставляемых представлений внутри сцены. Modal workflow, подтверждение и viewport-level Inspector по-прежнему используют `Window`.
@@ -228,6 +240,11 @@ Inspector владеет:
- вертикальным ритмом;
- раскладкой label/control.
В uncontrolled-режиме начальные секции задаёт `defaultOpen`. Для layout-aware
приложений Inspector поддерживает controlled-пару
`openSections/onOpenSectionsChange`: она позволяет общей кнопке Save сохранять
точную конфигурацию раскрытых секций и восстанавливать её без remount-default.
Для Engine Environment Settings desktop-контракт точный: окно `390 px`, рабочая колонка `330 px`, строка `154 + 14 + 162 px`, высота контрола `46 px`, section header `50 px` с радиусом `12 px`. Accent-filled section headers и полноширинные range/checker/select сохраняют геометрию исходника.
Engine продолжает владеть определениями полей, типами нод и сохранением значений. В живом catalog эти collapsible sections находятся в `Guideline → Контролы`; отдельной product-вкладки Inspector нет.
+64
View File
@@ -59,8 +59,70 @@ profile; renderer не содержит provider-specific условий.
`aspectId`. Соединение выполняется по стабильному `sourceId`; provider endpoint,
credentials и raw payload в Application Manifest не попадают.
В меню `Объекты` показываются только primary bindings. Joined aspects
(`technical profile`, `contacts`, `identity` и следующие типизированные
проекции) не являются самостоятельными слоями карты: они дополняют карточку
выбранного primary subject и потому не дублируются в меню. Если пользователь
переключает один subject на другой с совместимым detail profile, активная
вкладка карточки сохраняется; отсутствующие поля и секции не создаются.
Строка facet в окне объектов разделяет два действия. Клик по телу по-прежнему
применяет facet-фильтр, а отдельный chevron раскрывает список совпавших
subjects. Выбор subject в списке открывает его карточку и переносит текущую
композицию камеры к точке. Адаптер повторяет проверенную механику legacy
MMAP/AIS: сохраняет `heading`, `pitch`, `roll` и смещение камеры относительно
центра viewport; длительность перелёта для текущего канонического workflow —
`2.1 s`.
Facet-фильтры поддерживают мультивыбор: OR внутри одного facet и AND между facets. Missing facet означает отсутствие ограничения, а явно пустой список — ноль совпадений. Отжатие последнего chip не включает `Все`; `Все` включается и выключается только явным кликом. Переключение фильтра не двигает камеру, обзор выполняется отдельным действием.
## Platform reference layers
Станции метро, железнодорожные станции и вокзалы не являются L1/L2 workflow и
не материализуются как пользовательский Data Product. Это медленно меняющийся
provider-neutral справочный слой Platform Map Gateway:
```text
OSM/approved seed → Map Gateway spatial cache → map.station | map.terminal
→ reference-points → Foundry presentation profile → renderer
```
Контракт `transport-stations.v1` возвращает только allowlisted проекцию:
стабильный `sourceId`, нормализованный semantic type, категорию, названия,
оператора/сеть, допустимые официальные идентификаторы и координаты. Raw OSM
payload, upstream endpoint, credentials и произвольные tags в Foundry не
попадают. Начальный snapshot Москвы получен из legacy L1 MMAP как donor и
разделён на три категории: `metro`, `railway_station`,
`railway_terminal`. За пределами snapshot Map Gateway может последовательно
загрузить spatial cells из OSM и сохраняет нормализованный результат в
persistent cache; повторный viewport читается локально.
Page Layout хранит три независимых reference bindings:
- `reference.transport.metro``map.reference.transport.metro.v1`;
- `reference.transport.railway_terminal`
`map.reference.transport.terminal.v1`;
- `reference.transport.railway_station`
`map.reference.transport.railway-station.v1`.
Каждый профиль отдельно управляет видимостью, высотой таргета, размером точки,
подписью и высотой скрытия через обычный Inspector. Старые сохранённые Map
Layout получают зарегистрированные reference bindings и profiles при чтении;
их файл не переписывается до явного сохранения пользователем.
Тот же Page Layout хранит `inspectorOpenSections`. В текущем single-open
Inspector это пустой список либо id ровно одной раскрытой секции. Поэтому
закрытие окна настроек и повторное открытие не сбрасывает аккордеон, а общая
кнопка Application Save сохраняет его вместе с камерой и остальным layout.
Cesium adapter рендерит elevated targets и label plates как единый overlay над
terrain, buildings и imagery (`disableDepthTestDistance`). Цветная обводка
головки остаётся частью `elevated-spike` profile. Головка рисуется как
billboard до label-слоя, поэтому непрозрачная плашка нормально перекрывает
лежащий под ней маркер вместо «рентгеновского» проступания его обводки. Text
outline плашки отключён и не создаёт дополнительный halo. Контуры
`surface-fill` остаются частью отдельного контракта геозон.
## Acceptance fixtures
- `registry/fixtures/map/map-empty-offline-v0.1.json` — отсутствие provider/live data без разрушения shell и управляющих действий.
@@ -118,6 +180,8 @@ semantic types, field projection, `presentationProfileId`, optional
`subjectDetailProfileId`, `aspectId`/`joinToBindingId` и `slotId`: `points`
для live point entities, `zones` для polygon/multipolygon `map.zone` или
`subject-details` для non-spatial current aspects выбранного объекта.
Platform-owned справочные сущности используют отдельный `reference-points`
slot и не подменяют этот Data Product contract.
При открытии Application page browser делает same-origin запрос к Foundry:
```text
+3 -2
View File
@@ -95,8 +95,9 @@ Foundry и без неописанного generic JSON.
`foundry_save_map_page_view_state` является MCP-эквивалентом одной Application
кнопки `Сохранить`. Операция атомарно сохраняет camera/map height, typed patch
base settings, exact visibility/facet selections и все canonical binding
windows (`open`, `rect`, `maximized`, `zIndex`). Состояние keyed только по
base settings, точный список раскрытых секций Inspector, exact
visibility/facet selections и все canonical binding windows (`open`, `rect`,
`maximized`, `zIndex`). Состояние keyed только по
`bindingId`: editable `displayName` не участвует в identity. Missing facet не
ограничивает выборку, явно пустой список остаётся empty после reopen и не
мигрирует обратно в `Все`.