architect(ET-006): ADR-002/003, infra-requirements, data-requirements, tech-risks

This commit is contained in:
2026-05-22 03:38:47 +03:00
parent 2104f12d86
commit 62c2ee85dc
6 changed files with 573 additions and 2 deletions

View File

@@ -0,0 +1,143 @@
---
type: adr
work_item_id: ET-006
adr_id: ADR-002
title: "GPX-фича как отдельный модуль gpx.js"
status: accepted
date: 2026-05-22
authors:
- "agent:architect"
supersedes: null
superseded_by: 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()`:
```js
// в конце 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`

View File

@@ -0,0 +1,112 @@
---
type: adr
work_item_id: ET-006
adr_id: ADR-003
title: "Парсинг GPX — DOMParser в основном потоке с чанковой конвертацией"
status: accepted
date: 2026-05-22
authors:
- "agent:architect"
supersedes: null
superseded_by: null
---
# ADR-003: Парсинг GPX — `DOMParser` в основном потоке с чанковой конвертацией
## Контекст
- ТЗ REQ-F-02 **предписывает** использовать `DOMParser` (XML → DOM → GeoJSON).
- ТЗ NF-01: парсинг файла 50 МБ ≤ 5 с на устройстве с 4 ГБ RAM; во время
парсинга показывать индикатор загрузки; рендеринг 500K точек без видимых
фризов при pan/zoom.
- BRD (таблица рисков) упоминает Web Worker как **возможную** митигацию
(«при необходимости — Web Worker для парсинга») — не как требование.
Ключевое техническое ограничение, определяющее решение:
> **`DOMParser` — это API объекта `Window`. В `WorkerGlobalScope` его нет.**
В выделенном Web Worker нет DOM и нет `DOMParser``new DOMParser()` бросит
`ReferenceError`. Следовательно, парсинг в Worker **несовместим** с REQ-F-02:
он потребовал бы либо самописного XML-сканера, либо стороннего XML-парсера.
Это создаёт напряжение между REQ-F-02 (DOMParser) и идеей выноса парсинга
в Worker. Напряжение разрешается данным ADR.
## Решение
1. **Парсинг XML — в основном потоке** через `DOMParser.parseFromString`.
2. **Конвертация DOM → GeoJSON и расчёт статистики — чанками.**
Это итеративная часть, доминирующая по времени на больших файлах
(обход 500K+ DOM-узлов, чтение атрибутов, Haversine, дельты высот).
Обрабатывать порциями (например, по 510K точек), между порциями
отдавать управление event loop (`setTimeout(0)` / `requestAnimationFrame`
/ `requestIdleCallback`). Это сохраняет отзывчивость UI и анимацию
индикатора загрузки.
3. **Web Worker не используется.**
Атомарный вызов `parseFromString` заблокировать чанками нельзя — он блокирует
основной поток на время своего выполнения. Для реалистичных GPX (< 5 МБ) это
доли секунды; для предельного файла 50 МБ — порядка 12 с. Это **принимается**
(см. «Последствия» и риск R-1 в `10-tech-risks.md`).
## Рассмотренные альтернативы
### Альтернатива A: Web Worker + `DOMParser` (отклонена)
Невозможна технически: `DOMParser` отсутствует в `WorkerGlobalScope`.
### Альтернатива B: Web Worker + самописный строковый/regex XML-парсер (отклонена)
- Противоречит REQ-F-02 (предписан `DOMParser`).
- Добавляет в поддержку bespoke-парсер; крайние случаи GPX (namespaces,
XML-сущности, CDATA, экзотические кодировки) становятся нашей проблемой.
### Альтернатива C: Web Worker + сторонняя XML-библиотека (отклонена)
- Нарушает принцип «минимум зависимостей».
- Требует подключения npm-зависимости / сборщика, которых в проекте нет.
### Альтернатива D: `DOMParser` в основном потоке + синхронная конвертация (отклонена)
- Полная блокировка UI на всё время «парсинг + конвертация» — для больших
файлов это секунды полного фриза, индикатор загрузки не успевает
отрисоваться/обновиться.
### Альтернатива E: `DOMParser` в основном потоке + чанковая конвертация (выбрана)
- Ноль новых зависимостей, соответствует REQ-F-02.
- UI отзывчив на доминирующей (итеративной) части стоимости.
- Нет Worker-файла, нет потребности в CSP `worker-src`.
## Последствия
### Положительные
- Соответствие REQ-F-02, ноль новых зависимостей, нет сборщика/Worker-файла.
- CSS-индикатор загрузки анимируется потоком композитора независимо от
загрузки основного потока — крутится даже во время атомарного парса.
- UI отзывчив во время чанковой конвертации (основная по времени фаза).
### Отрицательные / митигации
| Последствие | Митигация |
|---|---|
| Атомарный `parseFromString` файла 50 МБ блокирует UI на ~12 с | Реалистичные GPX-файлы существенно меньше; 50 МБ — потолок валидации, а не норма; индикатор продолжает анимироваться. Зафиксировано как риск R-1. |
| Транзиентный пик памяти на 50 МБ (DOM ~150300 МБ) | DOM освобождается сразу после конвертации; рекомендуется обнулять ссылку на документ. См. риск R-3. |
| Если Worker когда-либо станет действительно необходим | Потребует замены `DOMParser` → отдельный ADR, superseding данный. |
### Влияние на компоненты
- **Frontend** (`gpx.js`) — реализация парсинга и чанковой конвертации.
- **Backend / API / OSRM / БД** — без изменений (парсинг полностью клиентский).
- **C4-диаграммы** — состав компонентов не меняется → обновление не требуется.
## Связанные
- ТЗ: `docs/work-items/ET-006/02-trz.md` (REQ-F-02, NF-01, §5)
- BRD: `docs/work-items/ET-006/01-brd.md` (§5, риск производительности)
- ADR-002: структура модуля `gpx.js`
- Риски: `docs/work-items/ET-006/10-tech-risks.md` (R-1, R-2, R-3)

View File

@@ -0,0 +1,89 @@
---
type: infra-requirements
work_item_id: ET-006
version: 1
status: approved
created_at: 2026-05-22
authors:
- "agent:architect"
---
# Infra Requirements — ET-006
ET-006 — **чисто фронтендовая** фича. Парсинг и хранение GPX полностью
клиентские (см. BRD §2, ADR-003). Бэкенд, БД, OSRM и сетевая конфигурация
**не затрагиваются**.
## 1. Сводка: инфраструктурных изменений нет
| Аспект | Изменение |
|---|---|
| Новые контейнеры | нет |
| Новые порты | нет |
| Новые тома (volumes) | нет |
| Изменения `docker-compose.yml` | нет |
| Изменения `Dockerfile` | нет |
| Изменения nginx / reverse proxy | нет |
| Пересборка OSRM-графа | не требуется |
| Миграции БД | нет (см. `08-data-requirements.md`) |
| Новые переменные окружения | нет |
| Новые внешние зависимости (npm/pip) | нет |
## 2. Новый статический ассет `gpx.js`
ADR-002 вводит новый файл `src/web/gpx.js`. Дополнительной конфигурации
не требуется:
- `Dockerfile` уже копирует каталог целиком: `COPY src/web/ ./src/web/`.
- `docker-compose.yml` монтирует `./src/web:/app/src/web` (dev).
- FastAPI отдаёт статику через `app.mount("/", StaticFiles(directory=STATIC_DIR,
html=True))` — файл доступен по `/gpx.js` автоматически.
- Подключение — тег `<script src="gpx.js">` в `index.html` (см. ADR-002).
## 3. Загрузка файла 50 МБ — без серверной нагрузки
ТЗ REQ-F-03 разрешает GPX-файлы до 50 МБ. Файл читается в браузере
(`<input type="file">` + `FileReader` / `File.text()`) и **никогда не
загружается на сервер**.
Следствия:
- Лимит `client_max_body_size` в nginx — **не релевантен** (нет upload).
- Ограничения размера тела запроса в FastAPI — **не релевантны**.
- Сетевой трафик и дисковая нагрузка сервера от ET-006 — нулевые.
## 4. CSP / заголовки безопасности
- CSP-заголовок в проекте сейчас не задаётся (проверено: `main.py`,
`index.html`). Дополнительных директив ET-006 не требует.
- Web Worker отклонён в ADR-003 → директива `worker-src` не нужна даже
при возможном будущем введении CSP.
- Сторонних CDN/доменов ET-006 не добавляет (парсинг нативный `DOMParser`,
профиль высот — нативный `<canvas>`).
## 5. Деплой
Стандартный процесс, без особых окон и без простоя сервисов:
1. Merge PR в trunk.
2. `make build` — пересборка Docker-образа (включает новый `gpx.js`).
3. `make deploy-test` → `docker compose up -d` на mva154.
4. Smoke-test на test-окружении
(`https://openclaw.mva154.duckdns.org/enduro/`):
- открыть `/gpx.js` — отдаётся 200;
- загрузить тестовый GPX, убедиться в отрисовке трека.
Простой `/api/route` и прочих API — **отсутствует** (бэкенд не меняется).
## 6. Rollback
Откат — обычный откат фронтенд-файлов (`index.html`, `app.js`, `app.css`,
`gpx.js`) через revert PR и повторную сборку образа. Серверного состояния,
БД-изменений или графа, которые надо откатывать, нет. Время отката
ограничено только временем пересборки/перезапуска контейнера (~12 мин).
## 7. CI
- ESLint покрывает новый `gpx.js` (цель `make lint`, уже включает eslint).
- Бэкенд-тесты (`pytest`) ET-006 не затрагивает.
- Пересборки графа в pipeline нет — не релевантно.

View File

@@ -0,0 +1,102 @@
---
type: data-requirements
work_item_id: ET-006
version: 1
status: approved
created_at: 2026-05-22
authors:
- "agent:architect"
---
# Data Requirements — ET-006
## 1. Сводка: персистентных данных нет
ET-006 не вводит серверного хранения. Все данные GPX-треков живут **только
в памяти браузера** на время сессии (BRD §3 «Out of scope», TRZ NF-04).
| Аспект | Решение |
|---|---|
| Схема БД (SQLite/Spatialite) | без изменений |
| Миграции (`migrations/`) | нет |
| Серверное хранение треков | нет |
| `localStorage` / `sessionStorage` | **не используется** (объём данных велик) |
| Время жизни данных | сессия; при перезагрузке страницы — потеря |
## 2. Входные данные
- Формат: **GPX 1.1**, namespace `http://www.topografix.com/GPX/1/1`
(с fallback на парсинг без namespace — TRZ REQ-F-02, тест U-08).
- Источник: локальный файл, выбранный пользователем
(`<input type="file" accept=".gpx" multiple>`).
- Ограничение размера: ≤ 50 МБ на файл (TRZ REQ-F-03).
- Извлекаемые сущности: `<trk>` / `<trkseg>` / `<trkpt>`, `<wpt>`, `<rte>`
(трактуется как трек).
## 3. Внутренняя модель (in-memory)
Каноническая модель — `window.gpxTracks` (массив загруженных файлов),
определена в TRZ §4. Владеет ею `gpx.js` (ADR-002):
```javascript
window.gpxTracks = [
{
id: 'gpx-<timestamp>', // уникальный ID
filename: 'morning_ride', // имя файла без расширения
color: '#e6194b', // цвет из палитры (8 цветов, цикл)
tracks: [
{
name: 'Morning Ride',
points: [[lon, lat, ele, time], ...],
stats: { distanceKm, elevGain, elevLoss, eleMin, eleMax }
}
],
waypoints: [ { lon, lat, name, ele } ],
sourceId: 'gpx-source-<timestamp>',
layerId: 'gpx-layer-<timestamp>',
waypointLayerId: 'gpx-wpt-<timestamp>'
}
];
```
Замечания для разработки:
- `ele` / `time` опциональны. При отсутствии `<ele>``ele = null`,
поля статистики высот = `null` (TRZ REQ-F-11, тесты U-05, U-14).
- `window.gpxTracks` — единственный источник правды; объекты карты
(source / layer / маркеры) **производны** от него и пересоздаются при
`map.setStyle()` через `rebuildGpxOverlays()` (REQ-F-13, ADR-002).
- Дополнительно потребуется состояние «активный трек» (для статистики и
профиля высот) — хранится в `gpx.js`, должно переживать смену стиля.
## 4. Объём данных в памяти
Предельный файл 50 МБ ≈ 500K+ точек. Ориентировочный транзиентный профиль:
| Стадия | Память (порядок) |
|---|---|
| Строка файла | ~50 МБ |
| DOM после `parseFromString` | ~150300 МБ (освобождается после конвертации) |
| Модель `points` + GeoJSON source | десятки МБ на файл |
TRZ REQ-F-07 не ограничивает число одновременных треков. Накопление
нескольких предельных файлов может исчерпать память вкладки на слабом
устройстве — см. риск R-3 в `10-tech-risks.md`.
## 5. Приватность
GPX-файлы могут содержать персональные данные (координаты поездок,
метки времени `<time>`). В ET-006 эти данные:
- **не передаются на сервер** (парсинг и хранение клиентские);
- **не сохраняются** между сессиями (нет БД, нет `localStorage`).
Следствие: серверных обязательств по хранению/удержанию/удалению
персональных данных ET-006 **не порождает**. Это сознательное проектное
свойство, а не упущение.
## 6. Выходные данные
ET-006 не экспортирует и не записывает данные (экспорт обратно в GPX —
out of scope, BRD §3). Профиль высот и статистика — производные расчёты,
существуют только в UI.

View File

@@ -0,0 +1,122 @@
---
type: tech-risks
work_item_id: ET-006
version: 1
status: approved
created_at: 2026-05-22
authors:
- "agent:architect"
---
# Technical Risks — ET-006
Технические риски этапа разработки. Бизнес-риски — в BRD §5.
Шкала: вероятность / влияние ∈ {низк., сред., выс.}.
## R-1 — Фриз UI при атомарном парсинге файла 50 МБ
- **Вероятность:** сред. · **Влияние:** низк.
- `DOMParser.parseFromString` нельзя разбить на чанки; для предельного
файла 50 МБ он блокирует основной поток на ~12 с (ADR-003).
- **Митигация:** реалистичные GPX существенно меньше; 50 МБ — потолок
валидации, а не норма; чанковая конвертация DOM→GeoJSON снимает
доминирующую часть стоимости; CSS-индикатор анимируется композитором.
- **Статус:** принят. Остаточный риск задокументирован в ADR-003.
## R-2 — Рендеринг трека 500K точек на слабом GPU/мобильном
- **Вероятность:** сред. · **Влияние:** сред.
- BRD §3 **явно запрещает** упрощение (simplify) точек. Значит штатного
рычага снижения нагрузки на рендер нет.
- **Митигация:** MapLibre оптимизирует GeoJSON line layers; единственная
доступная мера — принятие риска бизнесом (simplify вне scope).
Если на тестировании обнаружатся фризы pan/zoom — это повод для
отдельной задачи, а не для ET-006.
- **Статус:** принят бизнесом (ограничение BRD).
## R-3 — Исчерпание памяти при накоплении больших файлов
- **Вероятность:** низк. · **Влияние:** сред.
- TRZ REQ-F-07 не ограничивает число одновременных треков. Несколько
файлов по 50 МБ + транзиентные DOM-пики могут исчерпать память вкладки
на устройстве с 4 ГБ RAM.
- **Митигация:** обнулять ссылку на DOM-документ сразу после конвертации
(освобождение ~150300 МБ); не держать исходную строку файла после
парсинга. Реалистичный сценарий накопления десятков предельных файлов
маловероятен.
- **Статус:** принят с рекомендацией по управлению памятью разработке.
## R-4 — Дублирование обработчиков событий карты после `setStyle()`
- **Вероятность:** выс. · **Влияние:** сред.
- REQ-F-13: при смене стиля GPX-слои пересоздаются в `rebuildGpxOverlays()`.
Обработчики `map.on('click', layerId, fn)` / `mouseenter` / `mouseleave`
при повторной регистрации **накапливаются** — клик по треку начнёт
срабатывать многократно, утечка слушателей.
- **Митигация (для разработки):** перед повторной регистрацией снимать
старые обработчики (`map.off(...)` с сохранёнными ссылками на функции)
либо регистрировать делегированный обработчик один раз по префиксу ID
слоя, а не по конкретному ID. Покрыть тестом I-07.
- **Статус:** требует внимания на разработке.
## R-5 — Варианты namespace в GPX-файлах
- **Вероятность:** сред. · **Влияние:** низк.
- При запросе элементов через `getElementsByTagNameNS` файл без `xmlns`
вернёт пусто; при запросе без namespace — наоборот, возможны коллизии.
TRZ требует работать с GPX и с namespace, и без (тест U-08).
- **Митигация:** использовать namespace-агностичный обход — сопоставление
по `localName` элементов, а не по полному имени с namespace.
- **Статус:** требует внимания на разработке.
## R-6 — Невалидные координаты ломают `fitBounds`
- **Вероятность:** низк. · **Влияние:** низк.
- Точки `(0,0)`, NaN или координаты вне диапазона раздувают bbox и портят
автоцентрирование (REQ-F-06).
- **Митигация:** валидировать `lat ∈ [-90,90]`, `lon ∈ [-180,180]`,
числовой тип; отбрасывать невалидные точки до построения bbox.
- **Статус:** требует внимания на разработке.
## R-7 — Z-order GPX-слоёв относительно маршрута OSRM
- **Вероятность:** сред. · **Влияние:** низк.
- REQ-F-04 / REQ-F-13 / AC-10: GPX-линии должны быть **ниже** активного
маршрута OSRM, но выше базовых слоёв — и сохранять этот порядок после
`setStyle()`. Маршрут (`route-line-*`) и GPX рисуются в разных функциях;
порядок их вызова в `rebuildMapOverlays()` определяет итоговый z-order.
- **Митигация:** добавлять GPX line layer с явным `beforeId` — перед
слоями маршрута, если они есть; иначе поверх базовых. Не полагаться на
порядок вызовов в `rebuildMapOverlays()`. Покрыть тестом I-06.
- **Статус:** требует внимания на разработке.
## R-8 — Связность `gpx.js` ↔ `app.js` через глобали
- **Вероятность:** низк. · **Влияние:** низк.
- `gpx.js` зависит от глобалей `app.js` (`window._map`, `openSheet`,
`closeSheet`, `showToast`); `app.js` вызывает `rebuildGpxOverlays()`.
- **Митигация:** контракт интеграции зафиксирован в ADR-002; вызов из
`app.js` защищён `typeof`; `gpx.js` подключается после `app.js`.
- **Статус:** принят (контракт узкий и задокументирован).
## R-9 — Отсутствует переиспользуемый `showToast`
- **Вероятность:** низк. · **Влияние:** низк.
- Сейчас есть только частный `showRulerToast()` / `#ruler-toast`.
ET-006 требует несколько разных сообщений об ошибках.
- **Митигация:** обобщить toast до `showToast(message)` (ADR-002).
- **Статус:** принят (мелкий шаред-утиль на этапе разработки).
## Сводная таблица
| ID | Риск | Вер. | Влияние | Статус |
|----|------|------|---------|--------|
| R-1 | Фриз при парсинге 50 МБ | сред. | низк. | принят |
| R-2 | Рендеринг 500K точек | сред. | сред. | принят бизнесом |
| R-3 | Память при накоплении файлов | низк. | сред. | принят + рекомендация |
| R-4 | Дублирование обработчиков после setStyle | выс. | сред. | внимание разработки |
| R-5 | Варианты namespace GPX | сред. | низк. | внимание разработки |
| R-6 | Невалидные координаты в fitBounds | низк. | низк. | внимание разработки |
| R-7 | Z-order относительно маршрута OSRM | сред. | низк. | внимание разработки |
| R-8 | Связность gpx.js ↔ app.js | низк. | низк. | принят |
| R-9 | Нет переиспользуемого showToast | низк. | низк. | принят |