Files
enduro-trails/docs/work-items/ET-010/02-trz.md
claude-bot 9c81ebd653
All checks were successful
CI / lint (push) Successful in 4s
CI / test (push) Successful in 6s
CI / build (push) Successful in 1s
analyst(ET): auto-commit from analyst run_id=25
2026-05-31 17:13:21 +00:00

11 KiB
Raw Blame History

type, work_item_id, title, version, status, created_at, updated_at, authors, depends_on
type work_item_id title version status created_at updated_at authors depends_on
trz ET-010 ТЗ: Координаты центра карты в scale-zoom-bar 1 draft 2026-05-31 2026-05-31
agent:analyst
ET-010/01-brd.md

Техническое задание — ET-010: Координаты центра карты

1. Общая архитектура изменений

Изменение чисто фронтовое. Никакого нового кода в src/api/, миграций или Docker-настроек не вносится. Затрагиваются ровно два файла:

  • src/web/app.js — расширение существующей функции updateScaleZoom() (около строки 1411) и инициализация суб-элемента .szb-coords рядом с .szb-scale / .szb-zoom.
  • src/web/app.css — стили .szb-coords (по образцу .szb-zoom).

src/web/index.html не трогаем: #scale-zoom-bar создаётся динамически в initMap(), в HTML его нет.

2. Функциональные требования

REQ-F-01. Суб-элемент .szb-coords

В initMap() (src/web/app.js, рядом со строкой 1408), при сборке #scale-zoom-bar, добавить третий блок:

<div class="szb-scale"></div>
<div class="szb-zoom">z7</div>
<div class="szb-coords" aria-hidden="true">55.500, 40.500</div>

Порядок строго scale → zoom → coords (слева направо в flex-строке). Стартовое значение — текущий центр карты после new maplibregl.Map(...).

REQ-F-02. Формат координат

"<lat>, <lon>"

Где:

  • lat = map.getCenter().lat.toFixed(3) (три знака после точки)
  • lon = map.getCenter().lng.toFixed(3) (три знака после точки)
  • Разделитель — запятая + один пробел (", ")
  • Знак минус сохраняется ("-0.100")
  • Без подписей N/E/S/W

Никаких других форматов не поддерживается.

REQ-F-03. Обновление в updateScaleZoom()

Внутри существующей updateScaleZoom() после блока обновления zoomEl (строка 1457) добавить:

const coordsEl = scaleZoomBar.querySelector('.szb-coords');
if (coordsEl) {
  const c = map.getCenter();
  const next = `${c.lat.toFixed(3)}, ${c.lng.toFixed(3)}`;
  if (coordsEl.textContent !== next) {
    coordsEl.textContent = next;
  }
}

Условие if (coordsEl.textContent !== next) обязательно — оно исключает лишнюю работу с DOM при move-событиях, которые не сдвинули центр карты заметно (например, конец инерции).

REQ-F-04. Подключение к событиям

Никаких новых обработчиков навешивать не нужно. Существующие map.on('zoom', updateScaleZoom) и map.on('move', updateScaleZoom) (строки 1463-1464) автоматически вызывают updateScaleZoom() при любом изменении вьюпорта, включая panTo, flyTo, easeTo, zoomIn, zoomOut, перетаскивание мышью, pinch-zoom на тач-устройстве и программный map.jumpTo(...).

REQ-F-05. Поведение при setStyle() (тема)

#scale-zoom-bar создаётся через document.getElementById('map').appendChild(...) и не принадлежит DOM MapLibre — setStyle() его не затрагивает. После переключения темы updateScaleZoom() вызывается следующим же map.on('move') или map.on('zoom') — и текст координат корректно обновляется. Дополнительных вызовов в rebuildMapOverlays() не нужно.

REQ-F-06. Стили .szb-coords

В src/web/app.css после блока .szb-zoom { … } (после строки 926) добавить:

.szb-coords {
  font-size: 10px;
  font-weight: 500;
  color: rgba(255,255,255,0.85);
  text-shadow: 0 0 3px rgba(0,0,0,0.8), 0 1px 2px rgba(0,0,0,0.6);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

Семейство шрифта наследуется от body. tabular-nums гарантирует, что строка не «прыгает» по ширине при изменении цифр.

REQ-F-07. Доступность

  • .szb-coords получает атрибут aria-hidden="true" сразу при создании (см. REQ-F-01).
  • Никакого tabindex элемент не получает.
  • Не вмешивается в фокусную последовательность и не озвучивается скринридером.

REQ-F-08. Мобильный viewport

В существующем #scale-zoom-bar { display: flex; gap: 6px; } нет переноса строк — все три суб-элемента остаются в одну линию. Если на очень узких экранах (< 360 px) общая ширина превысит свободное место, браузер обрежет правый край .szb-coords (с nowrap) — это ожидаемое поведение dry-run, не требующее отдельной обработки.

В рамках ET-010 не вводим медиа-правил для скрытия .szb-coords на мобильном. Если регрессия по верстке всё же обнаружится — fix поедет отдельной задачей.

3. Нефункциональные требования

REQ-NF-01. Производительность

  • updateScaleZoom() вызывается синхронно из map.on('move') — это уже факт текущего кода. Добавляемая работа: один getCenter(), два toFixed(3), одна конкатенация, одно сравнение, опционально одно присваивание textContent. Все операции — O(1).
  • Никаких новых requestAnimationFrame / setTimeout / debounce не вводим.
  • Бюджет: суммарная работа на move не должна расти больше чем на +0.5 ms в среднем кадре (ориентир — performance.now() в DevTools).

REQ-NF-02. Совместимость браузеров

  • Поддержка: Chrome ≥ 110, Firefox ≥ 110, Safari ≥ 16, мобильные Chrome/Safari iOS ≥ 16 (как у текущего приложения).
  • Number.prototype.toFixed, Array.prototype.querySelector, template-strings — поддержаны во всех целевых браузерах с 2017 года.

REQ-NF-03. Логирование

  • В нормальной работе никакого вывода в console.*.
  • Никаких новых уровней логирования не вводим.

REQ-NF-04. Совместимость с другими work item

  • Не пересекается с ET-007 (satellite map): тот трогает style*.json, index.html, app.css, добавляет источник sat-raster. ET-010 не трогает ни источники, ни слои MapLibre.
  • Не пересекается с ET-009 (ScaleControl bottom-left): тот добавляет нативный MapLibre control в bottom-left. ET-010 расширяет custom-overlay #scale-zoom-bar в top-right. Разные DOM-узлы.

4. Структура данных

Никаких новых ключей localStorage. Никаких новых полей в layerState. Состояние существует только в DOM (textContent элемента .szb-coords) и пересчитывается из map.getCenter() при каждом вызове updateScaleZoom().

5. UI / UX контракт

Элемент Местоположение Поведение
#scale-zoom-bar top-right карты (absolute, top ≈ 8 px, right 12 px) Контейнер — не меняется визуально
.szb-scale левый элемент flex-строки Шкала «30 km» — не меняется
.szb-zoom средний элемент flex-строки «z7» — не меняется
.szb-coords правый элемент flex-строки «55.500, 40.500» — обновляется на move/zoom
Шрифт .szb-coords 10px / 500 / белый с тенью Идентичен .szb-label, чуть мельче .szb-zoom
aria-hidden "true" на .szb-coords Скринридер игнорирует элемент

6. Тестируемость

  • В tests/web/: unit-тест функции форматирования (extract formatCenterCoords(lat, lng) если оркестратор хочет — допустимо и инлайн-форматирование без extract).
  • Integration-тест (jsdom): проверка, что после имитации updateScaleZoom() .szb-coords.textContent соответствует ожидаемой строке.
  • E2E (Playwright) — см. 04-test-plan.yaml и 04b-ui-test-cases.md.

7. Совместимость и миграции

  • БД: миграций нет.
  • API: новых эндпоинтов нет.
  • Конфиги: новых ENV не добавляем.
  • Существующие work item: ET-005 (units), ET-006 (gpx), ET-007 (satellite), ET-009 (scale control) не затрагиваются.
  • Бизнес-конфиги, скрипты сборки, CI — не меняются.

8. Откат

В случае проблем:

  1. В src/web/app.js удалить из initMap() строку создания .szb-coords (часть scaleZoomBar.innerHTML = '<div…><div class="szb-coords"…></div>').
  2. Удалить из updateScaleZoom() блок обновления coordsEl (5 строк).
  3. В src/web/app.css удалить блок .szb-coords { … }.

Полный откат — один коммит, без побочных эффектов. Существующие шкала и зум-индикатор продолжат работать как раньше.