diff --git a/docs/work-items/ET-008/00-business-request.md b/docs/work-items/ET-008/00-business-request.md
new file mode 100644
index 0000000..713e54d
--- /dev/null
+++ b/docs/work-items/ET-008/00-business-request.md
@@ -0,0 +1,51 @@
+---
+type: business-request
+work_item_id: ET-008
+title: "GPS-треки с публичных платформ на карте"
+created_at: 2026-06-01
+source: plane
+requester: Слава
+---
+
+# Бизнес-запрос — ET-008
+
+## Исходная формулировка
+
+> Хочу видеть на карте GPS-треки с публичных платформ (OSM, чужие ссылки
+> на GPX), а не только локальные файлы. Минимум: вставить ссылку на
+> GPX-файл — увидеть трек. Дальше — поиск чужих публичных треков в
+> видимой области карты, чтобы перед поездкой посмотреть, кто и где ездил.
+
+## Контекст и ограничения
+
+1. ET-006 уже даёт инфраструктуру отображения GPX-треков (модель,
+ рендеринг, sheet, профиль высот). Эту инфраструктуру переиспользуем.
+2. В стеке нет авторизации пользователей и БД с user accounts —
+ платформы с обязательным OAuth (Strava, Komoot) **вне scope MVP**.
+3. Платный API Wikiloc — **вне scope MVP**.
+4. CORS не позволяет браузеру тянуть GPX напрямую с большинства
+ платформ — нужен прокси через FastAPI.
+5. Rate limits публичных API (OSM, Overpass) — нужен server-side кэш.
+
+## Решения аналитика (по умолчанию, при отсутствии явных уточнений)
+
+| Вопрос | Решение | Обоснование |
+|--------|---------|-------------|
+| Платформы MVP | OSM Public GPS Traces + универсальный GPX-по-URL | Открытые API без авторизации, бесплатные, покрывают сценарии «свой трек по ссылке» и «чужие треки рядом» |
+| Сценарии | (1) импорт по URL; (2) bbox-поиск треков в видимой области | Минимальный полезный набор, не требующий новых разделов UI |
+| Хранение | Сессия (как ET-006) + server-side LRU-кэш на бэкенде | Не вводим БД и аккаунты; кэш защищает от rate limits |
+| Auth | Нет | Все запросы — публичные данные |
+| Платформы post-MVP | Wikiloc API, Strava OAuth, Komoot OAuth | Будут отдельными work item, когда появится система аккаунтов |
+
+## Уточнения
+
+1. URL-импорт должен работать с любой прямой ссылкой на `.gpx`-файл
+ (GitHub raw, gist, личный сайт, веб-сервер пользователя).
+2. Поиск по OSM-трекам ограничен видимой областью карты (bbox).
+ Глобальный поиск не требуется.
+3. Загруженные с публичных платформ треки попадают в тот же sheet
+ `#sheet-gpx`, что и локальные GPX, и ведут себя идентично (статистика,
+ профиль высот, удаление, fit bounds, переживание смены стиля).
+4. Источник трека (URL / OSM trace id) сохраняется в модели и
+ отображается в карточке трека для пользователя.
+5. Кэш на сервере — TTL 24 часа, не персистентный (in-memory).
diff --git a/docs/work-items/ET-008/01-brd.md b/docs/work-items/ET-008/01-brd.md
new file mode 100644
index 0000000..821f8bd
--- /dev/null
+++ b/docs/work-items/ET-008/01-brd.md
@@ -0,0 +1,98 @@
+---
+type: brd
+work_item_id: ET-008
+title: "BRD: GPS-треки с публичных платформ на карте"
+version: 1
+status: draft
+created_at: 2026-06-01
+updated_at: 2026-06-01
+authors:
+ - "agent:analyst"
+---
+
+# BRD — ET-008: GPS-треки с публичных платформ на карте
+
+## 1. Цель
+
+Дать пользователю возможность увидеть на карте Enduro Trails GPS-треки
+с публичных источников без скачивания файлов вручную: либо вставив
+прямую ссылку на GPX, либо найдя чужие публичные треки в видимой
+области карты (через OSM Public GPS Traces).
+
+## 2. Контекст
+
+- ET-006 реализовал клиентский GPX-стек: парсер, модель
+ `window.gpxTracks`, sheet `#sheet-gpx`, статистика, профиль высот,
+ переживание `map.setStyle()` через `rebuildGpxOverlays()`. Источник
+ данных — только локальный файл пользователя.
+- Roadmap-фаза PH-3 «Smart Route» включает работу с GPX (импорт/экспорт).
+- В стеке нет пользовательских аккаунтов и БД пользователей. Платформы с
+ обязательным OAuth (Strava, Komoot) поэтому вне scope текущей итерации.
+- Браузер не может тянуть GPX напрямую с большинства публичных платформ
+ из-за CORS. OSM API не разрешает кросс-доменные запросы → прокси
+ через FastAPI обязателен.
+- OSM Public GPS Traces — открытый бесплатный источник публичных
+ GPS-треков, формат GPX, есть bbox-поиск, нет авторизации для чтения.
+
+## 3. Scope
+
+### In scope
+
+| # | Функция |
+|------|---------|
+| F-01 | Поле ввода URL прямой ссылки на GPX в `#sheet-gpx` |
+| F-02 | Импорт GPX по URL через прокси-эндпоинт `/api/gpx/fetch` |
+| F-03 | Кнопка «Найти публичные треки» в `#sheet-gpx` — поиск в bbox видимой области карты |
+| F-04 | Прокси-эндпоинт `/api/gpx/osm/traces` для OSM Public GPS Traces |
+| F-05 | Список найденных OSM-треков с метаданными (длина, точек, описание, автор) |
+| F-06 | Импорт выбранного OSM-трека одним тапом |
+| F-07 | Серверный LRU-кэш ответов внешних API (TTL 24 ч, in-memory) |
+| F-08 | Источник трека (URL / OSM trace id + ссылка) виден в карточке трека |
+| F-09 | Лимит размера загруженного по URL файла: 50 МБ (как ET-006) |
+| F-10 | Внятные сообщения об ошибках (CORS-фейл, 404, лимит API, битый GPX) |
+| F-11 | Импортированные треки попадают в общий список `window.gpxTracks` и неотличимы от локальных по поведению |
+
+### Out of scope
+
+- OAuth-интеграции (Strava, Komoot)
+- Платный API Wikiloc
+- Поиск треков глобально (без bbox)
+- Сохранение треков в БД между сессиями
+- Подписки на пользователей других платформ
+- Загрузка собственных треков на публичные платформы
+
+## 4. Метрики успеха
+
+| Метрика | Критерий |
+|---------|----------|
+| URL-импорт | Прямая ссылка на GPX до 50 МБ загружается за ≤ 5 сек на средней сети |
+| OSM-поиск bbox | Запрос видимой области возвращает результат за ≤ 3 сек (с кэшем — мгновенно) |
+| Точность | OSM-трек после импорта визуально совпадает с тем же треком из osm.org |
+| Кэш | Повторный запрос той же области/URL в течение 24 ч — без обращения к внешнему API |
+| UX | Все ошибки (CORS, 404, лимит, формат) — внятные toast-уведомления, не падение |
+| Совместимость с ET-006 | Локальные и удалённые треки в одном списке, поведение идентично |
+| Сохранение при смене стиля | Импортированные треки переживают переключение тёмной темы и слоёв рельефа |
+
+## 5. Риски
+
+| Риск | Вероятность | Влияние | Митигация |
+|------|-------------|---------|-----------|
+| OSM API rate limit (1 запрос / IP / сек) | Высокая | Среднее | Серверный кэш по bbox + дебаунс на клиенте |
+| URL-прокси превращается в open redirect / SSRF | Средняя | Высокое | Whitelist схем (http/https), блок приватных IP, лимит размера, таймаут |
+| Большие OSM-страницы (1000+ треков) → длинный список | Средняя | Низкое | Пагинация: показывать первые N, кнопка «ещё» |
+| GPX по URL не существует / 404 | Высокая | Низкое | Toast с понятной ошибкой |
+| Content-Type не `application/gpx+xml` | Высокая | Низкое | Проверять по содержимому (DOMParser), не по заголовкам |
+| Чужой публичный трек содержит вредоносный XML / XXE | Низкая | Высокое | DOMParser в браузере (XXE отключён), на бэкенде — `defusedxml` |
+| Внешний API внезапно недоступен | Средняя | Низкое | Graceful degradation: показать сообщение, не блокировать другие функции |
+
+## 6. Зависимости
+
+- **ET-006** — модель `window.gpxTracks`, рендеринг, sheet `#sheet-gpx`,
+ парсер `parseGpx()`. Без ET-006 эта задача не имеет смысла.
+- **Backend (FastAPI)** — новые эндпоинты `/api/gpx/fetch`,
+ `/api/gpx/osm/traces`, добавление `httpx` (уже есть) и `defusedxml`
+ (новая зависимость, опционально — для server-side валидации).
+- Внешние сервисы:
+ - `https://api.openstreetmap.org/api/0.6/trackpoints` — публичный API
+ OSM, ограничения: 1 req/sec/IP, 5000 точек/страница, до 5 страниц.
+ - Произвольные HTTPS-хосты (для URL-импорта) — без SLA, fail-soft.
diff --git a/docs/work-items/ET-008/02-trz.md b/docs/work-items/ET-008/02-trz.md
new file mode 100644
index 0000000..a2ef347
--- /dev/null
+++ b/docs/work-items/ET-008/02-trz.md
@@ -0,0 +1,473 @@
+---
+type: trz
+work_item_id: ET-008
+title: "ТЗ: GPS-треки с публичных платформ на карте"
+version: 1
+status: draft
+created_at: 2026-06-01
+updated_at: 2026-06-01
+authors:
+ - "agent:analyst"
+---
+
+# ТЗ — ET-008: GPS-треки с публичных платформ на карте
+
+## 1. Функциональные требования
+
+### REQ-F-01: Расширение sheet `#sheet-gpx`
+
+В верхней части `#sheet-gpx` (под header, над списком треков) добавить
+секцию «Источники» с двумя вкладками-кнопками (segmented control):
+
+- **Из файла** — текущее поведение ET-006 (`#btn-gpx-upload`).
+- **По ссылке** — поле ввода URL + кнопка «Загрузить».
+- **Найти рядом** — кнопка «Найти публичные треки в этой области карты».
+
+При первом открытии активна вкладка **Из файла** (обратная совместимость).
+
+### REQ-F-02: Импорт по URL
+
+- Поле `` с placeholder
+ «https://example.com/track.gpx».
+- Кнопка `#btn-gpx-fetch-url` рядом — «Загрузить».
+- При нажатии:
+ 1. Клиентская валидация URL (`new URL()`, схема `https?:`).
+ 2. Запрос `GET /api/gpx/fetch?url=`.
+ 3. Полученный текст GPX парсится тем же `parseGpx()` из `gpx.js`.
+ 4. Результат добавляется в `window.gpxTracks` как обычно. Поле
+ `source` = `{kind: 'url', url: ''}`.
+ 5. `filename` для отображения: последний segment URL без `.gpx` или
+ `` если есть.
+- Поддерживается также Enter в поле ввода.
+
+### REQ-F-03: Прокси-эндпоинт `/api/gpx/fetch`
+
+```
+GET /api/gpx/fetch?url=
+```
+
+- Валидация:
+ - Схема URL ∈ {`http`, `https`}.
+ - Хост резолвится в публичный IP (не RFC1918, не loopback, не link-local).
+ Проверка через `socket.getaddrinfo()` + `ipaddress.ip_address().is_global`.
+ - Запрет редиректов на приватные IP (`httpx.AsyncClient(follow_redirects=False)`,
+ ручная обработка max 3 редиректов с повторной валидацией хоста).
+- Загрузка:
+ - Таймаут 15 секунд.
+ - Лимит размера ответа: 50 МБ (стримом, прервать при превышении).
+ - Заголовок `User-Agent: enduro-trails/1.0 (+https://openclaw.mva154.duckdns.org/enduro/)`.
+- Кэш:
+ - Ключ = SHA-256(url).
+ - In-memory LRU, max 64 записи, TTL 24 ч.
+ - При cache hit — отдаётся из кэша.
+- Ответ:
+ - `200 OK`, `Content-Type: application/gpx+xml`, тело GPX.
+ - Заголовок `X-Cache: HIT|MISS`.
+- Ошибки → JSON `{error: "..."}`:
+ - `400` — невалидный URL / приватный IP / запрещённая схема.
+ - `404` — внешний сервер вернул 404.
+ - `413` — превышен лимит размера.
+ - `502` — внешний сервер недоступен / таймаут.
+ - `504` — таймаут на нашей стороне.
+
+### REQ-F-04: Кнопка «Найти публичные треки»
+
+- Кнопка `#btn-gpx-find-nearby` в секции «Источники».
+- Текст: «Найти треки в этой области».
+- При нажатии:
+ 1. Получить bbox видимой области карты: `map.getBounds()`.
+ 2. Валидация: площадь bbox ≤ 0.25 deg² (OSM API limit — иначе ошибка).
+ Если больше — toast «Слишком большая область, увеличьте zoom».
+ 3. Запрос `GET /api/gpx/osm/traces?bbox=west,south,east,north`.
+ 4. Открыть подсекцию «Найденные треки» (REQ-F-05).
+
+### REQ-F-05: Прокси-эндпоинт `/api/gpx/osm/traces`
+
+```
+GET /api/gpx/osm/traces?bbox=,,,&page=
+```
+
+- Параметры:
+ - `bbox` — обязательный, 4 числа через запятую.
+ - `page` — опциональный, целое ≥ 0, default 0.
+- Валидация:
+ - Каждая координата — валидный float, в допустимом диапазоне.
+ - Площадь bbox ≤ 0.25 deg² — иначе `400`.
+- Запрос к OSM:
+ ```
+ GET https://api.openstreetmap.org/api/0.6/trackpoints
+ ?bbox=&page=
+ ```
+ - Таймаут 10 секунд.
+ - User-Agent как в REQ-F-03.
+- Парсинг ответа:
+ - OSM возвращает GPX 1.0 с `` и атрибутом `gpx_id` у некоторых
+ точек (см. формат OSM API). Группируем точки по `gpx_id` →
+ массив треков-метаданных.
+ - Анонимные треки (без `gpx_id`) объединяются в один общий «Анонимные треки этой области».
+- Кэш:
+ - Ключ = `(bbox_rounded_to_4_digits, page)`.
+ - In-memory LRU, max 256 записей, TTL 24 ч.
+- Ответ (JSON):
+ ```json
+ {
+ "bbox": [w, s, e, n],
+ "page": 0,
+ "has_more": false,
+ "tracks": [
+ {
+ "osm_id": 12345,
+ "name": "Trail in the woods",
+ "description": "...",
+ "user": "username",
+ "points_count": 320,
+ "distance_km": 12.4,
+ "url": "https://www.openstreetmap.org/user/.../traces/12345",
+ "gpx_url": "https://api.openstreetmap.org/api/0.6/gpx/12345/data"
+ }
+ ]
+ }
+ ```
+- Поле `distance_km` — посчитано на сервере (Haversine).
+- Ошибки → JSON `{error: "..."}`:
+ - `400` — невалидный bbox / слишком большая область.
+ - `502` — OSM API недоступен.
+ - `504` — таймаут.
+
+### REQ-F-06: UI списка найденных треков
+
+В подсекции `#gpx-nearby-results` под кнопкой «Найти треки»:
+
+- Заголовок: «Найдено N треков в этой области».
+- Список карточек, каждая:
+ - Иконка-индикатор источника (OSM-логотип маленький).
+ - Имя трека (или «Без названия»).
+ - Метаданные: длина (км, через `units.js`), автор (если есть).
+ - Кнопка «Показать» — импортирует трек на карту.
+ - Кнопка «↗» — открывает страницу трека на osm.org в новой вкладке.
+- Если `has_more` — кнопка «Показать ещё» внизу списка (увеличивает page).
+- Если треков нет — текст «В этой области нет публичных GPS-треков».
+
+### REQ-F-07: Импорт выбранного OSM-трека
+
+При клике на «Показать»:
+1. Запрос `GET /api/gpx/fetch?url=` — тот же эндпоинт, что для
+ произвольного URL (переиспользование кэша и валидации).
+2. После загрузки — те же шаги, что REQ-F-02 (парсинг → модель → рендеринг).
+3. Поле `source` = `{kind: 'osm', osm_id: , url: }`.
+4. Карточка в списке найденных треков получает индикатор «✓ Загружен».
+5. Повторный клик «Показать» — no-op (toast «Уже загружен»).
+
+### REQ-F-08: Отображение источника в карточке трека
+
+В существующей карточке трека в списке `#gpx-list` (ET-006):
+
+- Под именем файла мелким шрифтом добавить строку «источник»:
+ - Локальный файл: «📁 локальный файл» (без изменения для ET-006).
+ - URL: «🔗 » (например, «🔗 github.com»).
+ - OSM: «🌍 OSM #» — кликабельная ссылка на страницу osm.org.
+
+### REQ-F-09: Расширение модели `window.gpxTracks`
+
+Каждый элемент `window.gpxTracks` дополнительно содержит:
+
+```javascript
+{
+ // ... существующие поля ET-006 (id, filename, color, tracks, waypoints, ...)
+ source: {
+ kind: 'file' | 'url' | 'osm',
+ url: string | null, // для kind='url' и 'osm'
+ osm_id: number | null, // для kind='osm'
+ }
+}
+```
+
+Для треков ET-006 (загруженных из файла) `source.kind = 'file'`
+(обратная совместимость через миграцию на лету: если `source` отсутствует,
+читать как `{kind: 'file'}`).
+
+### REQ-F-10: Обработка ошибок и toast-уведомления
+
+| Ситуация | Toast |
+|----------|-------|
+| Невалидный URL | «Невалидная ссылка» |
+| URL → приватный IP | «Эта ссылка недоступна» |
+| Внешний 404 | «Файл не найден по этой ссылке» |
+| Внешний таймаут / 502 | «Сервер не отвечает, попробуйте позже» |
+| Файл > 50 МБ | «Файл слишком большой (макс. 50 МБ)» |
+| Не GPX (DOMParser fail) | «По этой ссылке не GPX-файл» |
+| OSM: bbox > 0.25 deg² | «Слишком большая область, увеличьте zoom» |
+| OSM: 0 треков | «В этой области нет публичных GPS-треков» (не toast, а inline-сообщение) |
+| OSM: rate limit (429) | «Слишком много запросов к OSM, попробуйте через минуту» |
+
+### REQ-F-11: Сохранение при смене стиля карты
+
+Импортированные треки переживают `map.setStyle()` через тот же механизм
+`rebuildGpxOverlays()`, что и локальные ET-006. Никаких изменений в
+этой функции не требуется — модель данных совместима.
+
+## 2. Нефункциональные требования
+
+### REQ-NF-01: Безопасность
+
+- Прокси `/api/gpx/fetch` защищён от SSRF (REQ-F-03):
+ - Whitelist схем.
+ - Резолв и проверка хоста на публичность.
+ - Ручная обработка редиректов с повторной валидацией.
+ - Лимит размера ответа стримом.
+- Парсинг XML на бэкенде (если потребуется — для OSM-ответа) через
+ `defusedxml.ElementTree` — защита от XXE / billion laughs.
+- Парсинг GPX на клиенте — нативный `DOMParser`, XXE отключён по умолчанию.
+- CORS на новых эндпоинтах — наследуется от существующей конфигурации
+ (`allow_origins=["*"]`), отдельных правил не требуется.
+
+### REQ-NF-02: Производительность
+
+- Запрос OSM с кэш-хитом: ≤ 50 мс.
+- Запрос OSM без кэша: ≤ 3 сек (зависит от OSM API).
+- URL-импорт GPX 1 МБ: ≤ 2 сек.
+- URL-импорт GPX 50 МБ: ≤ 10 сек (с учётом сети).
+- Bbox-валидация и серилизация на бэкенде: ≤ 5 мс.
+
+### REQ-NF-03: Кэширование
+
+- LRU-кэш `/api/gpx/fetch`: 64 записи × до 50 МБ = до 3.2 ГБ памяти —
+ **слишком много**. Решение: хранить только treki ≤ 5 МБ, остальные не
+ кэшировать. Корректировка: кэш до 64 записей размером ≤ 5 МБ каждая.
+- LRU-кэш `/api/gpx/osm/traces`: 256 записей × ≤ 200 КБ JSON ≈ 50 МБ.
+- Оба кэша — in-memory, не персистентные, теряются при рестарте контейнера.
+- TTL: 24 часа.
+- Метрики кэша (`/api/health`): `gpx_fetch_cache_size`, `gpx_osm_cache_size`.
+
+### REQ-NF-04: Совместимость
+
+- Браузеры: те же, что ET-006 (Chrome 90+, Firefox 90+, Safari 15+).
+- Мобильные: input type=url с режимом клавиатуры url.
+- Backend: Python 3.12, FastAPI, httpx (уже есть), `defusedxml` (новая).
+
+### REQ-NF-05: UX
+
+- Во время сетевого запроса показывать индикатор (повторно используем
+ `#gpx-loading` из ET-006).
+- Кнопка «Найти треки» дизейблится во время запроса.
+- Все toast-уведомления — через существующий механизм `showToast()` из `gpx.js`.
+
+## 3. UI-спецификация
+
+### 3.1 Расширение `#sheet-gpx` — секция «Источники»
+
+```
+┌─────────────────────────────────────┐
+│ ═══ (handle) │
+│ 📄 GPX-треки [свернуть]│
+├─────────────────────────────────────┤
+│ ИСТОЧНИКИ │
+│ [📁 Из файла] [🔗 По ссылке] [🌍 Найти рядом] │
+│ │
+│ ─ если активна «По ссылке»: ─ │
+│ ┌──────────────────────────┐ ┌────┐ │
+│ │https://example.com/...gpx│ │Загр│ │
+│ └──────────────────────────┘ └────┘ │
+│ │
+│ ─ если активна «Найти рядом»: ─ │
+│ [ Найти треки в этой области карты ]│
+│ Найдено 5 треков: │
+│ ┌─────────────────────────────────┐ │
+│ │🌍 Trail in the woods [Показ.] │ │
+│ │ 12.4 км · автор: user42 [↗] │ │
+│ └─────────────────────────────────┘ │
+│ ┌─────────────────────────────────┐ │
+│ │🌍 Без названия [✓ Загр.]│ │
+│ │ 3.1 км · аноним [↗]│ │
+│ └─────────────────────────────────┘ │
+│ [ Показать ещё ] │
+├─────────────────────────────────────┤
+│ ЗАГРУЖЕННЫЕ ТРЕКИ (как в ET-006) │
+│ 🔴 morning.gpx [✕] │
+│ 📁 локальный файл │
+│ 🔵 trail_woods [✕] │
+│ 🌍 OSM #12345 │
+│ 🟢 strava-export [✕] │
+│ 🔗 github.com │
+└─────────────────────────────────────┘
+```
+
+### 3.2 Segmented control «Источники»
+
+- Контейнер: `