Files
enduro-trails/docs/work-items/ET-006/06-adr/ADR-002-gpx-module-structure.md

8.1 KiB
Raw Permalink Blame History

type, work_item_id, adr_id, title, status, date, authors, supersedes, superseded_by
type work_item_id adr_id title status date authors supersedes superseded_by
adr ET-006 ADR-002 GPX-фича как отдельный модуль gpx.js accepted 2026-05-22
agent:architect
null null

ADR-002: GPX-фича как отдельный модуль gpx.js

Контекст

ET-006 добавляет самодостаточную фичу: парсинг GPX, внутренняя модель, управление source/layer/маркерами карты, bottom sheet sheet-gpx, canvas-профиль высот, расчёт статистики. Оценка объёма — ~600900 строк JS.

ТЗ (02-trz.md §6) оставляет структуру файлов открытой и предлагает два варианта: дописать в app.js либо вынести в отдельный gpx.js. ТЗ ссылается на units.js как на прецедент.

Фактическое состояние кодовой базы (проверено):

  • src/web/units.js не существует. app.js — единственный JS-файл фронтенда: 113 КБ, ~2900 строк.
  • app.js подключён как классический скрипт (<script src="app.js">), не как ES-модуль. Все функции — глобальные; обработчики событий навешаны через инлайновые onclick="..." в index.html.
  • Сборщика (bundler) нет. Статика раздаётся как есть через FastAPI StaticFiles (mount /), Docker копирует каталог целиком (COPY src/web/ ./src/web/).

То есть «прецедент units.js» в ТЗ — иллюстративная неточность, но решение о структуре файлов ТЗ явно делегирует этому этапу. Принимается архитектурно.

Решение

Реализовать GPX-фичу в новом файле src/web/gpx.js, подключённом классическим <script> после app.js в index.html.

gpx.js владеет:

  • парсингом GPX (см. ADR-003);
  • внутренней моделью window.gpxTracks (см. 08-data-requirements.md);
  • управлением объектами карты: source / line layer / waypoint layers / маркеры;
  • логикой sheet-gpx (список треков, активный трек);
  • canvas-профилем высот и расчётом статистики (Haversine, набор/сброс высот).

Контракт интеграции (единственная поверхность связности)

gpx.js потребляет глобали, объявленные в app.js:

Символ Назначение
window._map экземпляр MapLibre
openSheet(id) / closeSheet(id) управление bottom sheet
showToast(msg) уведомления об ошибках (см. ниже)

app.js получает ровно одну новую строку — хук в rebuildMapOverlays():

// в конце rebuildMapOverlays()
if (typeof rebuildGpxOverlays === 'function') rebuildGpxOverlays();

Хук защищён typeof, поэтому app.js остаётся валидным и без gpx.js. Это закрывает REQ-F-13 (восстановление GPX-слоёв после map.setStyle()).

Toast-хелпер

Сейчас существует только #ruler-toast + showRulerToast() — частный случай. ET-006 требует несколько разных сообщений (REQ-F-02, REQ-F-03). Рекомендуется обобщить до переиспользуемого showToast(message) (элемент-контейнер + автоскрытие 4 с, стиль как у #ruler-toast — TRZ §3.4). Конкретная реализация — на этапе разработки; дизайн toast — за дизайнером.

Изменения подключения

  • index.html: добавить <script src="gpx.js"></script> после строки 400 (<script src="app.js">), плюс разметку (кнопка #btn-gpx-upload, #tb-gpx, #sheet-gpx) — см. TRZ §3.
  • Новый статический ассет gpx.js подхватывается автоматически: Docker уже копирует весь src/web/, FastAPI StaticFiles отдаёт по /gpx.js. Инфраструктурных изменений нет (см. 07-infra-requirements.md).

Рассмотренные альтернативы

Альтернатива A: дописать в app.js (отклонена)

  • app.js уже 113 КБ — добавление ~800 строк ухудшает читаемость и время ревью.
  • Смешение несвязанных доменов в одном файле.
  • Невозможно ревьюить/тестировать GPX-логику изолированно.

Альтернатива B: ES-модуль (<script type="module">) (отклонена)

  • Потребовала бы перевода всех инлайновых onclick-обработчиков на addEventListener и экспорт/импорт — большой blast radius на чужой код.
  • app.js не является модулем; смешивать module и classic-скрипты с общими глобалями хрупко.
  • Вне scope ET-006.

Альтернатива C: отдельный классический скрипт gpx.js (выбрана)

  • Изоляция домена, минимальная поверхность связности.
  • Совместимо с текущей моделью «всё глобальное, без сборщика».
  • Ревью и тесты ET-006 локализованы: gpx.js + 3 правки разметки в index.html + 1 строка в app.js.

Последствия

Положительные

  • Первое разбиение фронтенда на несколько файлов — задаёт лёгкий паттерн «одна фича = один классический скрипт + глобали» для будущих задач.
  • Поверхность ревью/тестирования ET-006 изолирована.

Отрицательные / митигации

Последствие Митигация
Порядок подключения: gpx.js после app.js Межфайловые вызовы происходят в момент события — функции уже определены. Единственный вызов app.js → gpx.js защищён typeof.
Связность через глобали (нет инкапсуляции) Контракт интеграции зафиксирован выше и узок (3 потребляемых символа + 1 хук).
Нет showToast — нужно обобщать #ruler-toast Небольшой шаред-утиль; реализация на этапе разработки.

Влияние на компоненты

  • Frontend — новый файл gpx.js, правки index.html, 1 строка в app.js.
  • Backend / API / OSRM / БД — без изменений.
  • C4-диаграммы — состав компонентов не меняется (фронтенд остаётся одним логическим компонентом) → обновление C4 не требуется. Отдельных .mmd в репозитории нет.

Связанные

  • ТЗ: docs/work-items/ET-006/02-trz.md (§6, REQ-F-13)
  • ADR-003: стратегия парсинга GPX
  • Данные: docs/work-items/ET-006/08-data-requirements.md
  • Инфра: docs/work-items/ET-006/07-infra-requirements.md
  • Риски: docs/work-items/ET-006/10-tech-risks.md