diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a1c8a5..7825a8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,3 +11,5 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) - Agent system prompts (architect, developer, reviewer, tester, deployer) - CI pipeline (Gitea Actions) - Docker configuration +- ET-002: чекбокс «POI» в попапе рельефа — показ/скрытие маркеров POI + с сохранением состояния в localStorage (ключ `poi-visible`) diff --git a/CLAUDE.md b/CLAUDE.md index 51242ca..eb1d8c6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -48,6 +48,22 @@ 6. Коммиты от имени claude-bot (git config user.name/email уже настроен). 7. Не использовать `--no-verify` без явного одобрения Owner. +## Фазы + +| # | Название | Описание | +|---|----------|----------| +| PH-1 | MVP | Карта грунтовок + MVT тайлы | +| PH-2 | Routing | OSRM роутинг + базовый UI | +| PH-3 | Smart Route | Альтернативы, статистика, GPX | +| PH-4 | Advanced Routing | Красивый маршрут, связка, разведка | +| PH-5 | Redesign | Тёмная тема, mobile UI, UX | +| PH-6 | Terrain | Hillshade + гипсометрия + TRI | +| PH-7 | Barriers | Шлагбаумы, тротуары, слой препятствий | +| PH-8 | Elevation Profile | Профиль высот, режим «Горка» | +| PH-9 | PWA | Офлайн режим | + +Детали каждой фазы: [docs/phases/](docs/phases/) + ## Данные - Terrain tiles: /home/slin/enduro-trails/data/terrain/ (hillshade, TRI, hypso) - OSM данные: /home/slin/enduro-trails/data/osm/ diff --git a/README.md b/README.md index 0e96dde..33616ce 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,22 @@ migrations/ — миграции БД .openclaw/ — system prompts агентов ``` +## Фазы + +| # | Название | Описание | +|---|----------|----------| +| PH-1 | MVP | Карта грунтовок + MVT тайлы | +| PH-2 | Routing | OSRM роутинг + базовый UI | +| PH-3 | Smart Route | Альтернативы, статистика, GPX | +| PH-4 | Advanced Routing | Красивый маршрут, связка, разведка | +| PH-5 | Redesign | Тёмная тема, mobile UI, UX | +| PH-6 | Terrain | Hillshade + гипсометрия + TRI | +| PH-7 | Barriers | Шлагбаумы, тротуары, слой препятствий | +| PH-8 | Elevation Profile | Профиль высот, режим «Горка» | +| PH-9 | PWA | Офлайн режим | + +Детали каждой фазы: [docs/phases/](docs/phases/) + ## Лицензия Данные: © OpenStreetMap contributors (ODbL) diff --git a/docs/README.md b/docs/README.md index 049669d..803bf59 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,3 +6,15 @@ - [design/](./design/) — дизайн-токены, компоненты - [operations/](./operations/) — runbook, мониторинг - [api/](./api/) — OpenAPI спецификация + +## Фазы + +- [PH-1.mvp](./phases/PH-1.mvp/) — MVP: карта грунтовок + MVT тайлы +- [PH-2.routing](./phases/PH-2.routing/) — OSRM роутинг + базовый UI +- [PH-3.smart-route](./phases/PH-3.smart-route/) — Альтернативы, статистика, GPX +- [PH-4.advanced-routing](./phases/PH-4.advanced-routing/) — Красивый маршрут, связка, разведка +- [PH-5.redesign](./phases/PH-5.redesign/) — Тёмная тема, mobile UI, UX +- [PH-6.terrain](./phases/PH-6.terrain/) — Hillshade + гипсометрия + TRI +- [PH-7.barriers](./phases/PH-7.barriers/) — Шлагбаумы, тротуары, слой препятствий +- [PH-8.elevation-profile](./phases/PH-8.elevation-profile/) — Профиль высот, режим «Горка» +- [PH-9.pwa](./phases/PH-9.pwa/) — Офлайн режим diff --git a/docs/phases/PH-1.mvp/00-phase-brd.md b/docs/phases/PH-1.mvp/00-phase-brd.md new file mode 100644 index 0000000..5f3ca59 --- /dev/null +++ b/docs/phases/PH-1.mvp/00-phase-brd.md @@ -0,0 +1,46 @@ +--- +type: phase-brd +phase_id: PH-1.mvp +title: "MVP: карта грунтовок ЦФО + MVT тайлы" +version: 1 +status: done +created_at: 2026-05-18 +authors: + - "agent:stream" +--- + +# PH-1 — MVP: карта грунтовок ЦФО + +## Цель + +Создать веб-карту с фокусом на грунтовые дороги: грунтовки и тропы — главный слой, асфальт — тусклый фон. Данные из OSM, отображение через MapLibre GL JS с кастомным стилем. + +## Scope + +- Загрузка OSM PBF (Geofabrik, ЦФО + Чувашия) +- Конвертация в SQLite/Spatialite (1.1M треков, 14K POI) +- Self-hosted MVT (vector tiles) через FastAPI +- MapLibre GL JS с кастомным стилем (грунтовки яркие, асфальт тусклый) +- Деплой через Docker Compose на mva154 + +## Что реализовано + +- FastAPI backend с эндпоинтами для MVT тайлов +- SQLite/Spatialite БД с данными ЦФО + Чувашия +- Кастомный стиль MapLibre: цветовая дифференциация по типу покрытия (track grade 1-5) +- Docker Compose конфигурация +- nginx reverse proxy (`/enduro/` → контейнер) + +## Ключевые решения + +| Решение | Причина | +|---------|---------| +| MapLibre GL JS (не Leaflet) | WebGL, производительность, vector tiles | +| Vanilla JS (не React) | Простота, нет build step, быстрый старт | +| FastAPI (не Django) | Лёгкий, async, минимум зависимостей | +| SQLite/Spatialite (не PostGIS) | Портативность, zero-config, достаточно для 1 региона | +| Self-hosted MVT (не TileServer GL) | Меньше зависимостей, контроль над фильтрацией | + +## Дата завершения + +02.05.2026 diff --git a/docs/phases/PH-2.routing/00-phase-brd.md b/docs/phases/PH-2.routing/00-phase-brd.md new file mode 100644 index 0000000..b582962 --- /dev/null +++ b/docs/phases/PH-2.routing/00-phase-brd.md @@ -0,0 +1,43 @@ +--- +type: phase-brd +phase_id: PH-2.routing +title: "Роутинг: OSRM с кастомным эндуро-профилем" +version: 1 +status: done +created_at: 2026-05-18 +authors: + - "agent:stream" +--- + +# PH-2 — Роутинг: «Дикий путь» + +## Цель + +Добавить построение маршрутов с приоритетом грунтовых дорог. Использовать OSRM с кастомным Lua-профилем, оптимизированным под эндуро. + +## Scope + +- Кастомный профиль `enduro.lua` для OSRM (приоритет грунтовок, штраф за асфальт) +- Пересборка OSRM графа из OSM PBF (~5.2 GB) +- Базовый UI для построения маршрута (точка А → Б) +- Отображение маршрута на карте + +## Что реализовано + +- OSRM с профилем enduro.lua (веса: track > path > unclassified > tertiary > secondary) +- API эндпоинт `/api/route` (FastAPI → OSRM) +- UI: клик по карте для установки точек старта/финиша +- Отображение маршрута (GeoJSON LineString на карте) +- Docker-сервис OSRM в compose + +## Ключевые решения + +| Решение | Причина | +|---------|---------| +| OSRM (не GraphHopper) | Быстрый, проверенный, кастомный lua-профиль | +| Кастомный enduro.lua | Стандартные профили не учитывают грунтовки как приоритет | +| Swap 6 GB | OSRM граф требует ~5.2 GB RAM | + +## Дата завершения + +03.05.2026 diff --git a/docs/phases/PH-3.smart-route/00-phase-brd.md b/docs/phases/PH-3.smart-route/00-phase-brd.md new file mode 100644 index 0000000..a6d3794 --- /dev/null +++ b/docs/phases/PH-3.smart-route/00-phase-brd.md @@ -0,0 +1,43 @@ +--- +type: phase-brd +phase_id: PH-3.smart-route +title: "Умный маршрут: альтернативы, статистика, GPX" +version: 1 +status: done +created_at: 2026-05-18 +authors: + - "agent:stream" +--- + +# PH-3 — Умный маршрут + +## Цель + +Предоставить пользователю выбор из нескольких вариантов маршрута с разным балансом грунт/асфальт. Показать статистику покрытия и дать возможность экспорта в GPX. + +## Scope + +- До 5 альтернативных маршрутов с разным балансом грунт/асфальт +- Промежуточные точки (до 8) +- Статистика покрытия (% по типам: track, path, unclassified, tertiary, secondary, primary) +- GPX экспорт (трек + waypoints) + +## Что реализовано + +- Мульти-запрос к OSRM с варьированием весов профиля +- UI выбора альтернатив (карточки с превью и статистикой) +- Drag-and-drop промежуточных точек на карте +- Панель статистики: дистанция, время, % грунта/асфальта по типам +- GPX экспорт с метаданными (имя маршрута, waypoints, timestamps) + +## Ключевые решения + +| Решение | Причина | +|---------|---------| +| Варьирование весов (не OSRM alternatives) | OSRM alternatives даёт похожие маршруты; варьирование весов — реально разные | +| До 8 промежуточных точек | Баланс между гибкостью и UX | +| GPX 1.1 формат | Совместимость с большинством навигаторов | + +## Дата завершения + +04.05.2026 diff --git a/docs/phases/PH-4.advanced-routing/00-phase-brd.md b/docs/phases/PH-4.advanced-routing/00-phase-brd.md new file mode 100644 index 0000000..19cde56 --- /dev/null +++ b/docs/phases/PH-4.advanced-routing/00-phase-brd.md @@ -0,0 +1,42 @@ +--- +type: phase-brd +phase_id: PH-4.advanced-routing +title: "Продвинутый роутинг: красивый маршрут, связка, разведка" +version: 1 +status: done +created_at: 2026-05-18 +authors: + - "agent:stream" +--- + +# PH-4 — Продвинутый роутинг + +## Цель + +Добавить три специализированных режима маршрутизации: «Красивый маршрут» (замкнутый круг через POI), «Связка» (соединить два трека грунтовками), «Разведка» (статистика грунтовок в радиусе). + +## Scope + +- **Красивый маршрут** — замкнутый круг заданной длины через живописные POI (озёра, виды, руины, броды) +- **Связка** — соединить два загруженных трека оптимальным грунтовым участком +- **Разведка** — показать статистику грунтовок в радиусе 20/50/100 км от точки + +## Что реализовано + +- Алгоритм «Красивый маршрут»: выбор POI в радиусе → TSP-оптимизация порядка → OSRM route +- UI загрузки GPX для режима «Связка» +- Алгоритм соединения: найти ближайшие точки двух треков → построить грунтовый мост +- «Разведка»: spatial query по SQLite → агрегация по типам → визуализация на карте (heatmap) +- Переключение режимов в toolbar + +## Ключевые решения + +| Решение | Причина | +|---------|---------| +| TSP через nearest-neighbor heuristic | Достаточно для 5-10 POI, O(n²) приемлемо | +| Spatialite для spatial queries | Уже есть в стеке, не нужен отдельный сервис | +| Радиусы 20/50/100 км | Покрывают типичные дневные маршруты | + +## Дата завершения + +04.05.2026 diff --git a/docs/phases/PH-5.redesign/00-phase-brd.md b/docs/phases/PH-5.redesign/00-phase-brd.md new file mode 100644 index 0000000..99134e4 --- /dev/null +++ b/docs/phases/PH-5.redesign/00-phase-brd.md @@ -0,0 +1,50 @@ +--- +type: phase-brd +phase_id: PH-5.redesign +title: "Редизайн: тёмная тема, mobile UI, UX" +version: 1 +status: done +created_at: 2026-05-18 +authors: + - "agent:stream" +--- + +# PH-5 — Редизайн + +## Цель + +Переработать UI/UX приложения: добавить тёмную/светлую тему с автопереключением, адаптировать под мобильные устройства, улучшить юзабилити (линейка, метки, поиск). + +## Scope + +- Тёмная/светлая тема (авто по SunCalc — переключение по закату/рассвету) +- Mobile UI: bottom sheets, toolbar, touch-оптимизация +- Линейка (измерение расстояний на карте) +- Метки (сохранение точек интереса, localStorage) +- Поиск (Nominatim geocoding) +- CSS custom properties для theming +- Синхронизация темы карты и UI + +## Что реализовано + +- Система тем: CSS custom properties, авто (SunCalc), ручное переключение +- Стиль карты MapLibre синхронизирован с UI-темой +- Bottom sheet для мобильных (маршрут, статистика, настройки) +- Toolbar с иконками (режимы, инструменты) +- Touch-события: long press для метки, swipe для bottom sheet +- Линейка: клик-клик для измерения, отображение дистанции +- Метки: создание, редактирование, удаление, persist в localStorage +- Поиск: Nominatim API, debounce, fly-to результату + +## Ключевые решения + +| Решение | Причина | +|---------|---------| +| SunCalc для авто-темы | Точное время заката для координат пользователя | +| CSS custom properties | Нативный theming без препроцессоров | +| Bottom sheets (не sidebar) | Мобильный паттерн, thumb-friendly | +| localStorage для меток | Простота, нет backend-зависимости | + +## Дата завершения + +05-06.05.2026 diff --git a/docs/phases/PH-6.terrain/00-phase-brd.md b/docs/phases/PH-6.terrain/00-phase-brd.md new file mode 100644 index 0000000..0ea904b --- /dev/null +++ b/docs/phases/PH-6.terrain/00-phase-brd.md @@ -0,0 +1,48 @@ +--- +type: phase-brd +phase_id: PH-6.terrain +title: "Рельеф: гипсометрия + hillshade + TRI" +version: 1 +status: done +created_at: 2026-05-18 +authors: + - "agent:stream" +--- + +# PH-6 — Рельеф + +## Цель + +Добавить визуализацию рельефа: гипсометрическая раскраска, hillshade (теневой рельеф), TRI (Terrain Ruggedness Index) для оценки сложности местности. + +## Scope + +- Загрузка и обработка SRTM 30м (NASA, public domain) +- Генерация raster tiles: гипсометрия, hillshade, TRI +- Раздача через nginx (статические файлы) +- UI: переключение слоёв рельефа, прозрачность +- Легенда для гипсометрии и TRI + +## Что реализовано + +- Pipeline обработки SRTM: скачивание → merge → reproject → tile generation +- Гипсометрия: цветовая шкала высот (зелёный → коричневый → белый) +- Hillshade: azimuth 315°, altitude 45°, z-factor 1.5 +- TRI: классификация (flat, nearly flat, slightly rugged, rugged, very rugged) +- Raster tiles zoom 8-14, формат PNG +- nginx location для раздачи tiles +- UI: layer switcher с opacity slider +- Легенда с цветовой шкалой + +## Ключевые решения + +| Решение | Причина | +|---------|---------| +| Raster tiles (не Mapbox Terrain RGB) | Простота генерации, nginx отдаёт статику | +| SRTM 30м | Бесплатно, достаточно для ЦФО (равнина) | +| Zoom 8-14 | Баланс между детализацией и объёмом данных | +| TRI как отдельный слой | Помогает оценить сложность без профиля высот | + +## Дата завершения + +12-14.05.2026 diff --git a/docs/phases/PH-7.barriers/00-phase-brd.md b/docs/phases/PH-7.barriers/00-phase-brd.md new file mode 100644 index 0000000..751c09f --- /dev/null +++ b/docs/phases/PH-7.barriers/00-phase-brd.md @@ -0,0 +1,51 @@ +--- +type: phase-brd +phase_id: PH-7.barriers +title: "Барьеры: исключить шлагбаумы и тротуары, слой препятствий" +version: 1 +status: active +created_at: 2026-05-18 +authors: + - "agent:stream" +--- + +# PH-7 — Барьеры + +## Цель + +Сделать роутинг безопасным: маршрут не проходит через физические препятствия (шлагбаумы) и запрещённые для мотоциклов дороги (тротуары, пешеходные зоны). Добавить визуальный слой препятствий на карту. + +## Scope + +### F-07: Исключить шлагбаумы из OSRM +- Ноды с `barrier=gate|bollard|lift_gate|chain|cycle_barrier|motorcycle_barrier|border_control|block` → `mode.inaccessible` в OSRM +- `cattle_grid` и `ford` — оставить (проезжие) + +### F-08: Исключить тротуары из OSRM +- Ways с `highway=footway|pedestrian|steps|corridor` → исключить из графа (return в process_way) + +### F-10: Слой препятствий на карте +- Визуализация шлагбаумов, ворот, блоков на карте +- Иконки по типу барьера +- Popup с информацией (тип, OSM ID) + +## Метрики успеха + +- Маршрут через точку с шлагбаумом → OSRM обходит или возвращает "не найден" +- Маршрут в городе → не проходит по тротуарам +- Время пересборки графа ≤ 60 мин +- Существующие маршруты без шлагбаумов/тротуаров — не ломаются + +## Зависимости + +- OSRM граф (пересборка с обновлённым enduro.lua) +- OSM PBF данные (уже есть) +- Work item: [ET-001](../../work-items/ET-001/) + +## Риски + +| Риск | Митигация | +|------|-----------| +| Пересборка графа ~40 мин (сервис недоступен) | Пересобирать ночью или в low-traffic | +| Слишком много заблокированных нод → маршруты не строятся | cattle_grid и ford оставлены; тестировать на реальных маршрутах | +| OSRM RAM при пересборке | Swap 6 GB уже настроен | diff --git a/docs/phases/PH-8.elevation-profile/00-phase-brd.md b/docs/phases/PH-8.elevation-profile/00-phase-brd.md new file mode 100644 index 0000000..ce51885 --- /dev/null +++ b/docs/phases/PH-8.elevation-profile/00-phase-brd.md @@ -0,0 +1,35 @@ +--- +type: phase-brd +phase_id: PH-8.elevation-profile +title: "Профиль высот на маршруте, режим «Горка»" +version: 1 +status: planned +created_at: 2026-05-18 +authors: + - "agent:stream" +--- + +# PH-8 — Профиль высот + +## Цель + +Показать профиль высот построенного маршрута. Добавить режим «Горка» — поиск участков с максимальным перепадом высот (подъёмы/спуски). + +## Scope + +- Профиль высот: график elevation по дистанции для построенного маршрута +- Данные высот из SRTM 30м (уже есть в PH-6) +- Интерактивный график: hover → маркер на карте +- Статистика: набор высоты, сброс высоты, макс/мин высота +- Режим «Горка»: поиск участков с перепадом > N метров в радиусе + +## Зависимости + +- PH-6 (SRTM данные, terrain pipeline) +- PH-2/PH-3 (роутинг, маршруты) + +## Открытые вопросы + +- Формат графика: Canvas или SVG? +- Сэмплирование точек: каждые N метров или адаптивное? +- «Горка»: порог перепада настраиваемый или фиксированный? diff --git a/docs/phases/PH-9.pwa/00-phase-brd.md b/docs/phases/PH-9.pwa/00-phase-brd.md new file mode 100644 index 0000000..64d121a --- /dev/null +++ b/docs/phases/PH-9.pwa/00-phase-brd.md @@ -0,0 +1,37 @@ +--- +type: phase-brd +phase_id: PH-9.pwa +title: "PWA: офлайн режим, кэширование тайлов" +version: 1 +status: planned +created_at: 2026-05-18 +authors: + - "agent:stream" +--- + +# PH-9 — PWA офлайн + +## Цель + +Сделать приложение доступным офлайн: Service Worker для кэширования, предзагрузка тайлов для выбранного региона, сохранение маршрутов локально. + +## Scope + +- Service Worker: кэширование статики (JS, CSS, шрифты, иконки) +- Предзагрузка тайлов: выбрать область на карте → скачать тайлы для офлайн +- Кэширование vector tiles (MVT) и raster tiles (terrain) +- Офлайн-роутинг: сохранение построенных маршрутов для просмотра без сети +- Install prompt (Add to Home Screen) +- Manifest.json + +## Зависимости + +- PH-1 (MVT тайлы) +- PH-6 (raster terrain tiles) +- Все предыдущие фазы (UI, роутинг) + +## Открытые вопросы + +- Лимит кэша (сколько тайлов можно скачать?) +- Стратегия инвалидации кэша +- Офлайн-роутинг: только просмотр сохранённых или локальный OSRM? diff --git a/docs/phases/pilot/00-phase-brd.md b/docs/phases/pilot/00-phase-brd.md deleted file mode 100644 index 0034cb1..0000000 --- a/docs/phases/pilot/00-phase-brd.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -type: phase-brd -phase_id: pilot -title: "Enduro Trails — пилотный проект мультиагентной разработки" -version: 1 -status: approved -created_at: 2026-05-15 -authors: - - "agent:stream" - - "human:slava" ---- - -# BRD — Enduro Trails (пилотный проект) - -## 1. Цель и метрика успеха - -**Цель:** Создать веб-приложение для планирования эндуро-маршрутов с визуализацией грунтовых дорог, рельефа и навигацией — как пилотный проект мультиагентной системы разработки. - -**Метрики успеха:** -- Рабочее приложение доступно по URL (https://openclaw.mva154.duckdns.org/enduro/) -- Покрытие региона: ЦФО + Чувашия (1.1M треков, 14K POI) -- Построение маршрута < 5 секунд -- Мобильный UI (PWA-ready) -- Агентный конвейер: фича от постановки до деплоя ≤ 4 часа - -## 2. Стейкхолдеры - -| Роль | Кто | Интерес | -|------|-----|---------| -| Заказчик / Owner | Слава | Использует для планирования поездок | -| Analyst | Стрим (OpenClaw) | BRD, ТЗ, координация | -| Разработка | Claude Code CLI агенты | Architect, Developer, Reviewer, Tester, Deployer | - -## 3. Scope - -### В скоупе -- Карта грунтовых дорог (MapLibre GL JS, кастомный стиль) -- Роутинг «Дикий путь» (OSRM, кастомный профиль enduro.lua) -- Альтернативные маршруты (до 5 вариантов) -- Промежуточные точки (до 8) -- Статистика покрытия (% грунт/асфальт по типам) -- «Красивый маршрут» (замкнутый круг через POI) -- «Связка» (соединить два трека) -- «Разведка» (грунтовки в радиусе) -- Рельеф (гипсометрия + hillshade, SRTM 30м) -- TRI (Terrain Ruggedness Index) -- Линейка, метки, GPX экспорт -- Тёмная/светлая тема (авто по SunCalc) -- Мобильный UI (bottom sheets, toolbar, touch) -- Поиск (Nominatim geocoding) - -### Вне скоупа (v1) -- PWA офлайн режим -- GPS-трекинг в реальном времени -- Народные треки (Wikiloc, Komoot) -- Профиль высот на маршруте -- Мультирегион (вся Россия) -- Нативное мобильное приложение - -## 4. Архитектура - -### Компоненты -- **Frontend** — MapLibre GL JS, vanilla JS (ES modules), CSS custom properties -- **Backend API** — FastAPI (Python 3.12), uvicorn (4 workers) -- **Tile Server** — статические raster tiles через nginx -- **Vector Tiles** — MVT из SQLite (self-hosted, FastAPI) -- **Routing Engine** — OSRM с кастомным профилем `enduro.lua` -- **Database** — SQLite + Spatialite (431 MB) -- **Reverse Proxy** — nginx (`/enduro/` → контейнер) - -### Инфраструктура -- Один сервер: mva154 (82.22.50.71) -- Docker Compose -- Gitea (git + CI) -- Plane (управление задачами) - -### Данные -- OSM PBF (Geofabrik, ЦФО + Чувашия) -- SRTM 30м (NASA, public domain) -- OSRM граф (~5.2 GB) - -## 5. Реализованные фазы - -| Фаза | Описание | Статус | Дата | -|------|----------|--------|------| -| 1 | MVP: карта + MVT тайлы | ✅ | 02.05.2026 | -| 2 | Роутинг + базовый UI | ✅ | 03.05.2026 | -| 3 | Умный маршрут (альтернативы, статистика, GPX) | ✅ | 04.05.2026 | -| 4 | Продвинутый роутинг (красивый, связка, разведка) | ✅ | 04.05.2026 | -| 5 | Редизайн (тёмная тема, mobile UI, UX) | ✅ | 05-06.05.2026 | -| 5.4 | Рельеф (hillshade + гипсометрия + TRI) | ✅ | 12-14.05.2026 | - -## 6. Бэклог - -| Фаза | Описание | Приоритет | -|------|----------|-----------| -| 3.1 | Улучшение роутинга (шлагбаумы, тротуары, слой препятствий) | Высокий | -| 6 | SRTM продвинутый (профиль высот, «Горка») | Средний | -| 7 | PWA + офлайн | Средний | -| 8 | Народные треки | Низкий | - -## 7. Ключевые решения - -| Решение | Причина | -|---------|---------| -| MapLibre GL JS (не Leaflet) | WebGL, производительность, vector tiles | -| Vanilla JS (не React) | Простота, нет build step, быстрый старт | -| FastAPI (не Django) | Лёгкий, async, минимум зависимостей | -| SQLite/Spatialite (не PostGIS) | Портативность, zero-config, достаточно для 1 региона | -| OSRM (не GraphHopper) | Быстрый, проверенный, кастомный lua-профиль | -| Self-hosted MVT (не TileServer GL) | Меньше зависимостей, контроль над фильтрацией | -| Raster tiles для terrain (не Mapbox Terrain RGB) | Простота генерации, nginx отдаёт статику | -| Docker Compose | Один файл — весь стек | - -## 8. Риски - -| Риск | Митигация | -|------|-----------| -| OSRM граф большой (5.2 GB RAM) | Swap 6 GB настроен | -| SQLite не масштабируется | Миграция на PostGIS при необходимости | -| Один сервер — single point of failure | Бэкапы, Docker restart policy | -| SRTM 30м недостаточно для крутых склонов | Достаточно для ЦФО (равнина) | diff --git a/docs/phases/pilot/01-phase-plan.md b/docs/phases/pilot/01-phase-plan.md deleted file mode 100644 index 2a9e4d6..0000000 --- a/docs/phases/pilot/01-phase-plan.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -type: phase-plan -phase_id: pilot -title: "План пилотной фазы" -version: 1 -status: active ---- - -# План пилотной фазы — Enduro Trails - -## Текущий статус - -Проект в стадии перехода от прототипа к управляемой мультиагентной разработке. - -### Выполнено (прототип, 02-14.05.2026) -- Фазы 1-5.4 реализованы вручную (Стрим + Dev-агент) -- Все основные фичи работают -- Приложение доступно по URL - -### В процессе (мультиагентная инфраструктура, 15.05.2026) -- ✅ Репо в Gitea с канонической структурой -- ✅ Claude Code CLI авторизован -- ✅ Service account claude-bot -- ✅ Branch protection -- ✅ CI pipeline -- ✅ System prompts агентов -- 🔄 Миграция прототипа в репо - -### Следующие шаги -1. Merge миграции в main -2. Первая задача через полный агентный конвейер (Фаза 1 мультиагентного BRD) -3. Orchestrator MVP (Фаза 2 мультиагентного BRD) - -## Приоритет фич (бэклог) - -1. **F-07 + F-08** — Исключить шлагбаумы и тротуары из OSRM (пересборка графа) -2. **F-10** — Слой препятствий на карте -3. **F-09** — Больше альтернатив (penalized re-query) -4. Профиль высот на маршруте -5. PWA офлайн diff --git a/docs/work-items/ET-002/00-business-request.md b/docs/work-items/ET-002/00-business-request.md new file mode 100644 index 0000000..afeb911 --- /dev/null +++ b/docs/work-items/ET-002/00-business-request.md @@ -0,0 +1,20 @@ +--- +type: business-request +work_item_id: ET-002 +title: "Чекбокс показа/скрытия POI на карте" +created_at: 2026-05-20 +source: telegram +requester: Слава +--- + +# Исходный запрос — ET-002 + +## Формулировка заказчика + +> На карте сейчас всегда отражаются маркеры POI. Нужен в кнопке рельефа добавить чекбокс показывать/не показывать POI. + +## Контекст + +- Канал: Telegram +- Дата: 2026-05-19 +- Приоритет: не указан (обычный) diff --git a/docs/work-items/ET-002/01-brd.md b/docs/work-items/ET-002/01-brd.md new file mode 100644 index 0000000..c8b83f4 --- /dev/null +++ b/docs/work-items/ET-002/01-brd.md @@ -0,0 +1,61 @@ +--- +type: brd +work_item_id: ET-002 +title: "BRD: Чекбокс показа/скрытия POI в попапе рельефа" +version: 1 +status: approved +created_at: 2026-05-20 +authors: + - "agent:analyst" +--- + +# BRD — ET-002: Чекбокс показа/скрытия POI в попапе рельефа + +## 1. Цель + +Дать пользователю возможность скрывать маркеры POI на карте, чтобы они не загромождали обзор при планировании маршрута. Управление — через чекбокс в существующем попапе кнопки «Рельеф». + +## 2. Контекст + +- Сейчас POI (слои `poi-circles`, `poi-labels`) отображаются всегда при загрузке карты. +- Попап кнопки «Рельеф» (`terrain-popup`) уже содержит чекбоксы: Тени рельефа, Перепады, Грунтовки, Тропы. +- Механизм `toggleLayer('poi')` уже реализован в коде, но не привязан к UI в попапе. + +## 3. Scope + +### In scope + +- Добавить чекбокс «POI» в попап кнопки «Рельеф» (после секции «Тропы») +- POI включены по умолчанию (checked) +- Состояние чекбокса сохраняется в localStorage (ключ `poi-visible`) +- При загрузке страницы — восстанавливать состояние из localStorage +- Скрытие/показ слоёв `poi-circles` и `poi-labels` через MapLibre `setLayoutProperty` + +### Out of scope + +- Фильтрация POI по типу (кафе, заправки, и т.д.) +- Отдельная кнопка для POI вне попапа рельефа +- Изменение иконок или стилей POI +- Backend-изменения + +## 4. Метрики успеха + +| Метрика | Критерий | +|---------|----------| +| Чекбокс отображается в попапе | Визуально присутствует после «Тропы» | +| Снятие чекбокса скрывает POI | Слои `poi-circles` и `poi-labels` получают visibility: none | +| Установка чекбокса показывает POI | Слои получают visibility: visible | +| Состояние сохраняется | После перезагрузки страницы чекбокс и видимость POI соответствуют последнему выбору | +| Не ломает существующий функционал | Грунтовки, тропы, рельеф, роутинг работают как прежде | + +## 5. Риски + +| Риск | Вероятность | Митигация | +|------|-------------|-----------| +| Конфликт с существующим toggleLayer('poi') | Низкая | Использовать тот же механизм layerState, не дублировать логику | +| Попап становится слишком длинным на мобильных | Низкая | Один чекбокс — минимальное увеличение высоты | + +## 6. Зависимости + +- Нет внешних зависимостей +- Только фронтенд (vanilla JS + HTML) diff --git a/docs/work-items/ET-002/02-trz.md b/docs/work-items/ET-002/02-trz.md new file mode 100644 index 0000000..be7bbe6 --- /dev/null +++ b/docs/work-items/ET-002/02-trz.md @@ -0,0 +1,98 @@ +--- +type: trz +work_item_id: ET-002 +title: "ТЗ: Чекбокс показа/скрытия POI в попапе рельефа" +version: 1 +status: approved +created_at: 2026-05-20 +authors: + - "agent:analyst" +--- + +# ТЗ — ET-002: Чекбокс показа/скрытия POI в попапе рельефа + +## 1. Функциональные требования + +### REQ-F-01: Чекбокс POI в попапе рельефа + +Система должна отображать чекбокс «POI» в попапе кнопки «Рельеф» (`terrain-popup`), расположенный после чекбокса «Тропы» (`trails-path-cb`), отделённый горизонтальной линией (`
`). + +### REQ-F-02: Состояние по умолчанию + +При первом посещении (отсутствие ключа в localStorage) чекбокс POI должен быть включён (checked), маркеры POI видимы. + +### REQ-F-03: Скрытие POI + +При снятии чекбокса система должна установить `visibility: 'none'` для слоёв `poi-circles` и `poi-labels` через `map.setLayoutProperty()`. + +### REQ-F-04: Показ POI + +При установке чекбокса система должна установить `visibility: 'visible'` для слоёв `poi-circles` и `poi-labels`. + +### REQ-F-05: Сохранение состояния + +Система должна сохранять состояние чекбокса в `localStorage` под ключом `poi-visible` (значения: `'1'` — показывать, `'0'` — скрывать). + +### REQ-F-06: Восстановление состояния при загрузке + +При загрузке страницы система должна: +1. Прочитать значение `localStorage.getItem('poi-visible')` +2. Если значение `'0'` — снять чекбокс и скрыть слои POI +3. Если значение `'1'` или отсутствует (null) — установить чекбокс и показать слои POI + +### REQ-F-07: Синхронизация с layerState + +Изменение чекбокса должно обновлять `layerState.poi` (true/false), чтобы состояние было консистентно с внутренней моделью приложения. + +## 2. Нефункциональные требования + +### REQ-NF-01: Производительность + +Переключение видимости POI должно происходить мгновенно (< 50 мс), без перезагрузки тайлов. + +### REQ-NF-02: Совместимость + +Решение должно работать во всех браузерах, поддерживающих MapLibre GL JS (Chrome 80+, Firefox 78+, Safari 14+, Edge 80+). + +### REQ-NF-03: Мобильная адаптация + +Чекбокс должен быть доступен и нажимаем на мобильных устройствах (touch target ≥ 44px). + +### REQ-NF-04: Отсутствие регрессий + +Существующие чекбоксы (Тени рельефа, Перепады, Грунтовки, Тропы) должны продолжать работать без изменений. + +## 3. UI-спецификация + +### Расположение в HTML + +```html + +
+ +``` + +### Стилизация + +Использовать существующий класс `terrain-checkbox` — единообразие с остальными чекбоксами в попапе. + +## 4. Данные + +- localStorage ключ: `poi-visible` +- Значения: `'1'` (показывать) | `'0'` (скрывать) +- MapLibre слои: `poi-circles`, `poi-labels` +- Состояние в JS: `layerState.poi` + +## 5. API + +Нет изменений в backend API. Вся логика — клиентская. + +## 6. Затрагиваемые файлы + +| Файл | Изменение | +|------|-----------| +| `src/web/index.html` | Добавить чекбокс в `terrain-popup` | +| `src/web/app.js` | Добавить функцию `onPoiCheckbox()`, логику восстановления состояния при загрузке | diff --git a/docs/work-items/ET-002/03-acceptance-criteria.md b/docs/work-items/ET-002/03-acceptance-criteria.md new file mode 100644 index 0000000..ce706a1 --- /dev/null +++ b/docs/work-items/ET-002/03-acceptance-criteria.md @@ -0,0 +1,85 @@ +--- +type: acceptance-criteria +work_item_id: ET-002 +title: "Acceptance Criteria: Чекбокс POI" +version: 1 +status: approved +created_at: 2026-05-20 +authors: + - "agent:analyst" +--- + +# Acceptance Criteria — ET-002 + +## AC-01: Чекбокс отображается в попапе + +```gherkin +Given пользователь открыл карту +When пользователь нажимает кнопку «Рельеф» +Then в попапе отображается чекбокс «POI» после секции «Тропы» +And чекбокс отделён горизонтальной линией от секции выше +``` + +## AC-02: POI включены по умолчанию + +```gherkin +Given пользователь открыл карту впервые (нет ключа poi-visible в localStorage) +When попап рельефа открыт +Then чекбокс «POI» установлен (checked) +And маркеры POI видны на карте +``` + +## AC-03: Скрытие POI + +```gherkin +Given чекбокс «POI» установлен +And маркеры POI видны на карте +When пользователь снимает чекбокс «POI» +Then маркеры POI исчезают с карты +And слои poi-circles и poi-labels имеют visibility: none +``` + +## AC-04: Показ POI + +```gherkin +Given чекбокс «POI» снят +And маркеры POI скрыты +When пользователь устанавливает чекбокс «POI» +Then маркеры POI появляются на карте +And слои poi-circles и poi-labels имеют visibility: visible +``` + +## AC-05: Состояние сохраняется после перезагрузки + +```gherkin +Given пользователь снял чекбокс «POI» +When пользователь перезагружает страницу +Then чекбокс «POI» снят +And маркеры POI скрыты +``` + +## AC-06: Восстановление включённого состояния + +```gherkin +Given пользователь установил чекбокс «POI» +When пользователь перезагружает страницу +Then чекбокс «POI» установлен +And маркеры POI видны +``` + +## AC-07: Не ломает существующие чекбоксы + +```gherkin +Given попап рельефа открыт +When пользователь переключает чекбоксы «Тени рельефа», «Перепады», «Грунтовки», «Тропы» +Then каждый чекбокс работает как прежде +And чекбокс «POI» не влияет на их поведение +``` + +## AC-08: Синхронизация с layerState + +```gherkin +Given чекбокс «POI» снят +When внешний код читает layerState.poi +Then значение равно false +``` diff --git a/docs/work-items/ET-002/04-test-plan.yaml b/docs/work-items/ET-002/04-test-plan.yaml new file mode 100644 index 0000000..85d842d --- /dev/null +++ b/docs/work-items/ET-002/04-test-plan.yaml @@ -0,0 +1,116 @@ +--- +type: test-plan +work_item_id: ET-002 +title: "Test Plan: Чекбокс POI" +version: 1 +status: approved +created_at: 2026-05-20 +authors: + - "agent:analyst" +--- + +tests: + - id: TP-01 + type: unit + description: "onPoiCheckbox() устанавливает visibility слоёв" + steps: + - "Mock map.getLayer() → true" + - "Mock map.setLayoutProperty()" + - "Установить checkbox.checked = false" + - "Вызвать onPoiCheckbox()" + expected: + - "setLayoutProperty('poi-circles', 'visibility', 'none') вызван" + - "setLayoutProperty('poi-labels', 'visibility', 'none') вызван" + - "localStorage.setItem('poi-visible', '0') вызван" + - "layerState.poi === false" + + - id: TP-02 + type: unit + description: "onPoiCheckbox() показывает слои при checked=true" + steps: + - "Mock map.getLayer() → true" + - "Установить checkbox.checked = true" + - "Вызвать onPoiCheckbox()" + expected: + - "setLayoutProperty('poi-circles', 'visibility', 'visible') вызван" + - "setLayoutProperty('poi-labels', 'visibility', 'visible') вызван" + - "localStorage.setItem('poi-visible', '1') вызван" + - "layerState.poi === true" + + - id: TP-03 + type: unit + description: "Восстановление состояния при загрузке — POI скрыты" + steps: + - "localStorage.setItem('poi-visible', '0')" + - "Вызвать функцию инициализации POI" + expected: + - "checkbox.checked === false" + - "layerState.poi === false" + - "Слои скрыты" + + - id: TP-04 + type: unit + description: "Восстановление состояния при загрузке — POI видны (default)" + steps: + - "localStorage не содержит ключ poi-visible" + - "Вызвать функцию инициализации POI" + expected: + - "checkbox.checked === true" + - "layerState.poi === true" + + - id: TP-05 + type: e2e + description: "Чекбокс POI виден в попапе рельефа" + steps: + - "Открыть карту в браузере" + - "Нажать кнопку «Рельеф»" + expected: + - "В попапе виден чекбокс с текстом «POI»" + - "Чекбокс расположен после «Тропы», отделён линией" + + - id: TP-06 + type: e2e + description: "Переключение POI скрывает/показывает маркеры" + steps: + - "Открыть карту, дождаться загрузки POI" + - "Открыть попап рельефа" + - "Снять чекбокс POI" + - "Убедиться что маркеры исчезли" + - "Установить чекбокс POI" + - "Убедиться что маркеры появились" + expected: + - "Маркеры POI скрываются и появляются в соответствии с чекбоксом" + + - id: TP-07 + type: e2e + description: "Состояние POI сохраняется после перезагрузки" + steps: + - "Открыть карту" + - "Снять чекбокс POI" + - "Перезагрузить страницу" + - "Открыть попап рельефа" + expected: + - "Чекбокс POI снят" + - "Маркеры POI не отображаются" + + - id: TP-08 + type: integration + description: "Чекбокс POI не влияет на другие слои" + steps: + - "Включить Тени рельефа и Грунтовки" + - "Снять чекбокс POI" + expected: + - "Тени рельефа остаются видимыми" + - "Грунтовки остаются видимыми" + - "Только POI скрыты" + + - id: TP-09 + type: e2e + description: "Мобильная доступность чекбокса" + steps: + - "Открыть карту на мобильном viewport (375px)" + - "Открыть попап рельефа" + - "Тапнуть чекбокс POI" + expected: + - "Чекбокс нажимается без проблем" + - "Touch target достаточный (≥ 44px)" diff --git a/docs/work-items/ET-002/06-adr/adr-0001-poi-visibility-client-side.md b/docs/work-items/ET-002/06-adr/adr-0001-poi-visibility-client-side.md new file mode 100644 index 0000000..fffc5a1 --- /dev/null +++ b/docs/work-items/ET-002/06-adr/adr-0001-poi-visibility-client-side.md @@ -0,0 +1,139 @@ +--- +type: adr +work_item_id: ET-002 +adr_id: adr-0001 +title: "ADR-0001: Управление видимостью POI — клиентское решение на localStorage" +status: accepted +created_at: 2026-05-21 +authors: + - "agent:architect" +supersedes: [] +superseded_by: [] +labels: [] +--- + +# ADR-0001 — Управление видимостью POI: клиентское решение на localStorage + +## Статус + +Accepted + +## Контекст + +ET-002 добавляет в попап кнопки «Рельеф» (`terrain-popup`) чекбокс «POI», +позволяющий пользователю скрывать маркеры POI (слои `poi-circles`, +`poi-labels`), чтобы не загромождать карту при планировании маршрута. + +Существующее состояние кодовой базы (`src/web/app.js`): + +- объект `layerState = { tracks, paths, poi, basemap }` — внутренняя модель + видимости слоёв (строка 377), `poi: true` по умолчанию; +- карта групп слоёв `poi: ['poi-circles', 'poi-labels']` (строка 381); +- функция `toggleLayer(group)` уже переключает видимость через + `map.setLayoutProperty(..., 'visibility', ...)` (строки 385–389), но + завязана на кнопку-тулбар (`btn.classList.toggle('active', ...)`), а не на + чекбокс попапа; +- сложился устойчивый паттерн персистентности в `localStorage` со + значениями `'1'`/`'0'`: ключи `terrain-hillshade`, `terrain-tri`, + `trails-track`, `trails-path`, `enduro-theme-mode`, `MARKERS_KEY`. + +Backend (FastAPI) и БД (SQLite/Spatialite) к видимости POI отношения не +имеют — POI отдаются как часть тайлов/источника, видимость регулируется +исключительно на уровне рендера MapLibre. + +## Рассматриваемые варианты + +### Вариант A — Клиентское решение: `setLayoutProperty` + `localStorage` (выбран) + +Видимость переключается через `map.setLayoutProperty()` для слоёв +`poi-circles` и `poi-labels`; состояние хранится в `localStorage` под +ключом `poi-visible`; `layerState.poi` остаётся единственным источником +истины в рантайме. + +- **Плюсы:** нулевые изменения backend/БД/инфраструктуры; переключение + мгновенное (тайлы не перезагружаются — слой остаётся в источнике, + меняется только `layout.visibility`); полностью консистентно с уже + существующими чекбоксами попапа и принципом «минимум зависимостей». +- **Минусы:** настройка не синхронизируется между устройствами/браузерами + (для данной фичи это приемлемо и зафиксировано как Out of scope в BRD). + +### Вариант B — Хранение настройки на backend (профиль пользователя) + +Видимость POI сохраняется через новый API-эндпоинт в SQLite. + +- **Плюсы:** синхронизация между устройствами. +- **Минусы:** требует модели пользователя/сессий (в проекте отсутствует), + новый эндпоинт, миграцию БД, сетевой round-trip — несопоставимо + избыточно для одного UI-флага. Противоречит принципам BRD (минимум + зависимостей, нет необходимости в server-state). + +### Вариант C — Удаление/повторное добавление слоёв POI + +Скрытие через `map.removeLayer()`, показ через `map.addLayer()`. + +- **Плюсы:** нет «невидимых» слоёв в стиле. +- **Минусы:** дороже по производительности, риск рассинхрона порядка + слоёв и обработчиков событий (`map.on('click', 'poi-circles', ...)`, + строка 1515), усложняет код. `setLayoutProperty` — штатный механизм + MapLibre именно для этого сценария. + +## Решение + +Принимается **Вариант A**. + +1. **Видимость** слоёв `poi-circles` и `poi-labels` переключается через + `map.setLayoutProperty(layerId, 'visibility', 'visible' | 'none')`. +2. **Персистентность** — `localStorage`, ключ `poi-visible`, значения + `'1'` / `'0'`, в соответствии с уже сложившейся конвенцией проекта. +3. **Источник истины в рантайме** — `layerState.poi`. Обработчик + `onPoiCheckbox()` обязан синхронно обновлять `layerState.poi`, + `localStorage` и `layout.visibility` обоих слоёв. +4. **Без дублирования логики.** Логика установки видимости группы POI + должна переиспользовать существующую карту групп слоёв + (`poi: ['poi-circles','poi-labels']`). Допускается выделение общего + приватного хелпера (например `applyPoiVisibility(visible)`), + вызываемого как из `onPoiCheckbox()`, так и из восстановления + состояния при загрузке. `toggleLayer('poi')` (тулбар-кнопка) и + `onPoiCheckbox()` (чекбокс попапа) должны оставаться консистентны + через общий `layerState.poi` — недопустимо, чтобы кнопка и чекбокс + показывали разное состояние. +5. **Backend, БД, API, инфраструктура — без изменений.** Состав + компонентов C4 не меняется, обновление C4-диаграмм не требуется. + +## Последствия + +### Положительные + +- Изменение полностью клиентское: deploy без миграций и без изменения + состава контейнеров. +- Переключение < 50 мс (REQ-NF-01) — тайлы не перезагружаются. +- Конвенция `localStorage` остаётся единообразной — ниже когнитивная + нагрузка при поддержке. + +### Отрицательные / ограничения + +- Настройка локальна для браузера: при очистке `localStorage` или смене + устройства сбрасывается на дефолт (POI включены). Принято осознанно. +- В стиле карты остаются слои с `visibility: none` — это штатно для + MapLibre и не влияет на производительность. + +### Технический долг + +- `toggleLayer()` и `onPoiCheckbox()` частично дублируют намерение + «переключить группу слоёв». Если в будущих фазах появятся ещё + popup-чекбоксы для слоёв, стоит унифицировать тулбар-кнопки и + popup-чекбоксы в единый контроллер слоёв. На ET-002 — вне scope. + +## Классификация изменения + +Minor change. Новые сервисы/БД/эндпоинты не вводятся, состав компонентов +не меняется. Лейбл `arch:major-change` **не требуется**. Обязательного +архитектурного approve не требуется. + +## Связанные документы + +- `docs/work-items/ET-002/01-brd.md` +- `docs/work-items/ET-002/02-trz.md` +- `docs/work-items/ET-002/03-acceptance-criteria.md` +- `docs/work-items/ET-002/07-infra-requirements.md` +- `docs/architecture/README.md` diff --git a/docs/work-items/ET-002/07-infra-requirements.md b/docs/work-items/ET-002/07-infra-requirements.md new file mode 100644 index 0000000..0dced72 --- /dev/null +++ b/docs/work-items/ET-002/07-infra-requirements.md @@ -0,0 +1,94 @@ +--- +type: infra-requirements +work_item_id: ET-002 +title: "Инфраструктурные требования — ET-002: Чекбокс показа/скрытия POI" +version: 1 +status: approved +created_at: 2026-05-21 +authors: + - "agent:architect" +--- + +# Инфраструктурные требования — ET-002 + +## 1. Резюме + +ET-002 — изменение **исключительно фронтенда** (`src/web/index.html`, +`src/web/app.js`). Новой инфраструктуры не требуется. Документ +зафиксирован для полноты work-item и явно подтверждает отсутствие +инфра-воздействия (см. ADR-0001). + +## 2. Контейнеры и сервисы + +| Аспект | Требование | +|--------|------------| +| Новые контейнеры | Нет | +| Изменения существующих сервисов (api, osrm, nginx) | Нет | +| Изменения `docker-compose.yml` | Нет | +| Изменения `Dockerfile` | Нет | + +## 3. Сеть + +| Аспект | Требование | +|--------|------------| +| Новые порты | Нет | +| Изменения reverse proxy (nginx, `/enduro/`) | Нет | +| Новые внешние домены / DNS | Нет | +| Исходящие сетевые вызовы из фронтенда | Нет (вся логика локальная) | + +## 4. Хранилища данных + +| Аспект | Требование | +|--------|------------| +| Изменения схемы SQLite/Spatialite | Нет | +| Миграции БД (`migrations/`) | Нет | +| Серверное хранилище состояния | Нет | +| Клиентское хранилище | `localStorage`, ключ `poi-visible` (`'1'`/`'0'`), ≈ 1 байт полезной нагрузки на браузер | + +## 5. Конфигурация и секреты + +| Аспект | Требование | +|--------|------------| +| Новые переменные окружения | Нет | +| Новые секреты | Нет | +| Изменения конфигурации FastAPI / uvicorn | Нет | + +## 6. Зависимости + +| Аспект | Требование | +|--------|------------| +| Новые npm/Python пакеты | Нет | +| Новые внешние сервисы | Нет | +| Версия MapLibre GL JS | Без изменений (`setLayoutProperty` — штатный API) | + +## 7. Сборка и деплой + +- **Pipeline:** существующий Gitea Actions без изменений (`lint`, `test`, + `build`). +- **Артефакт:** статические ассеты фронтенда (`src/web/`). Деплой — + штатная пересборка/перевыкладка и `docker compose up -d` на mva154. +- **Простой (downtime):** нет — изменение только в статике фронтенда. +- **План отката:** обратный коммит (revert) и повторный деплой; + миграций/состояния, требующих отдельного отката, нет. + +## 8. Ресурсы (CPU / RAM / диск) + +Воздействие отсутствует. Переключение `layout.visibility` слоёв +выполняется в браузере клиента; тайлы не перезапрашиваются (REQ-NF-01). + +## 9. Наблюдаемость + +Новые метрики, логи и алерты не требуются. Поведение проверяется +e2e-тестами фронтенда согласно `04-test-plan.yaml`. + +## 10. Влияние на C4 + +Состав компонентов системы не меняется. Обновление +`docs/architecture/c4-*.mmd` не требуется (диаграммы C4 в репозитории +на данный момент отсутствуют — только `docs/architecture/README.md`). + +## 11. Вывод + +Инфраструктурных, сетевых, конфигурационных изменений и изменений БД +**нет**. ET-002 безопасен для деплоя в рамках обычного релизного цикла +фронтенда. Эскалация `arch:major-change` не требуется. diff --git a/docs/work-items/ET-002/09-review.md b/docs/work-items/ET-002/09-review.md new file mode 100644 index 0000000..9f4806b --- /dev/null +++ b/docs/work-items/ET-002/09-review.md @@ -0,0 +1,144 @@ +--- +type: review +work_item_id: ET-002 +title: "Code Review: Чекбокс показа/скрытия POI в попапе рельефа" +version: 2 +status: approved +verdict: APPROVED +created_at: 2026-05-21 +authors: + - "agent:reviewer" +--- + +# Code Review — ET-002 + +## Вердикт + +**APPROVED** — найдены только замечания уровня P3 (nice-to-have). +Блокеров (P0) и must-fix (P1) нет. + +## Объём ревью + +Проверены изменения в `src/web/app.js` и `src/web/index.html` против: + +- `docs/work-items/ET-002/02-trz.md` (ТЗ, v1, approved) +- `docs/work-items/ET-002/03-acceptance-criteria.md` (AC, v1, approved) +- `docs/work-items/ET-002/06-adr/adr-0001-poi-visibility-client-side.md` +- `docs/work-items/ET-002/04-test-plan.yaml` +- `CLAUDE.md` + +Дополнительно учтён юнит-тест `tests/unit/poi_toggle.test.js`. + +--- + +## 1. Соответствие ТЗ + +| Требование | Статус | Где реализовано | +|------------|--------|-----------------| +| REQ-F-01 — чекбокс «POI» в `terrain-popup` после `trails-path-cb`, отделён `
` | ✅ | `index.html:56-60` | +| REQ-F-02 — состояние по умолчанию: checked, POI видимы | ✅ | `index.html:58` (атрибут `checked`); `app.js:2848-2852` (`stored === null → poiOn = true`) | +| REQ-F-03 — снятие чекбокса → `visibility: 'none'` для `poi-circles`, `poi-labels` | ✅ | `app.js:2833-2837` → `applyPoiVisibility(false)` → `app.js:2815-2825` | +| REQ-F-04 — установка чекбокса → `visibility: 'visible'` | ✅ | те же функции, ветка `visible` | +| REQ-F-05 — сохранение в `localStorage['poi-visible']` (`'1'`/`'0'`) | ✅ | `app.js:2835` | +| REQ-F-06 — восстановление при загрузке (`'0'` скрыть / `'1'`\|null показать) | ✅ | `app.js:2847-2853` (`restorePoiState`) | +| REQ-F-07 — синхронизация `layerState.poi` | ✅ | `app.js:2816` (`layerState.poi = visible` в общем хелпере) | +| REQ-NF-01 — переключение < 50 мс, без перезагрузки тайлов | ✅ | используется `setLayoutProperty`, источник слоёв не трогается | +| REQ-NF-02 — совместимость с MapLibre GL JS | ✅ | штатные API MapLibre, без экзотики | +| REQ-NF-03 — мобильный touch target ≥ 44px | ⚠️ см. P3-2 | переиспользован класс `terrain-checkbox` (как предписано ТЗ §3) | +| REQ-NF-04 — отсутствие регрессий | ✅ | существующие чекбоксы и их обработчики не затронуты | + +UI-спецификация ТЗ §3 (разметка, `id="poi-visible-cb"`, `onchange="onPoiCheckbox()"`, +класс `terrain-checkbox`) воспроизведена в `index.html` дословно. + +**Вывод:** все функциональные требования выполнены. + +## 2. Соответствие ADR (adr-0001) + +| Пункт решения | Статус | Комментарий | +|---------------|--------|-------------| +| 1. Видимость через `map.setLayoutProperty(... 'visibility' ...)` | ✅ | `app.js:2822` | +| 2. Персистентность — `localStorage['poi-visible']`, `'1'`/`'0'` | ✅ | соответствует конвенции проекта (`trails-track`, `terrain-*`) | +| 3. Источник истины — `layerState.poi`; `onPoiCheckbox()` синхронно обновляет `layerState`, `localStorage`, `layout.visibility` | ✅ | `onPoiCheckbox` → `setItem` + `applyPoiVisibility` (правит `layerState` и оба слоя) | +| 4. Без дублирования: общий приватный хелпер, переиспользование `layerGroups.poi` | ✅ | выделен `applyPoiVisibility(visible)`, итерирует `layerGroups.poi`; используется и `onPoiCheckbox`, и `restorePoiState` — ровно как предлагает ADR | +| 5. Backend/БД/API/инфраструктура без изменений | ✅ | изменения чисто клиентские | + +Выбран и реализован Вариант A. Решение полностью соответствует ADR. +Замечание по консистентности с `toggleLayer('poi')` — см. P3-1. + +## 3. Качество кода + +Сильные стороны: + +- Изменения локализованы в явно размеченном блоке + `>>> ET-002 POI visibility block ... <<<` (`app.js:2800-2854`) — удобно + для ревью и для тест-харнеса. +- JSDoc на всех трёх функциях, есть ссылка на ADR в шапке блока. +- `applyPoiVisibility` имеет guard `if (!map) return` и проверку + `map.getLayer(id)` перед `setLayoutProperty` — устойчиво к раннему вызову + и к смене стиля. +- `restorePoiState` корректно интегрирован в существующую цепочку + восстановления: `style.load → onMapStyleLoad → rebuildMapOverlays` + (`app.js:131`) — POI восстанавливается и при первой загрузке, и при + переключении темы. Паттерн идентичен `restoreTrailsState`. +- `restorePoiState` не пишет в `localStorage` (восстановление не должно + иметь побочных эффектов) — поведение задокументировано в JSDoc и + покрыто тестом TP-03. +- Разделение ответственности: персистентность — только в `onPoiCheckbox`, + применение видимости — в общем хелпере. + +Замечаний P0/P1/P2 нет. + +## 4. Качество тестов + +`tests/unit/poi_toggle.test.js` исполняет **реальный** код из `app.js` +(блок извлекается по маркерам ET-002 и оборачивается через `new Function` +с мок-зависимостями) — это покрывает риск рассинхрона тестов и продакшн-кода. + +- TP-01 — снятие чекбокса: скрытие слоёв + `setItem('poi-visible','0')` + `layerState.poi=false` ✅ +- TP-02 — установка чекбокса: показ слоёв + `setItem('1')` + `layerState.poi=true` ✅ +- TP-03 — `restorePoiState()` при `'0'`: скрытие + чекбокс снят + без записи в `localStorage` ✅ +- TP-04 — `restorePoiState()` без ключа: дефолт «видимы» ✅ +- Доп.: значение `'1'`, изоляция чужих слоёв (дух TP-08), синхронизация + `layerState` без слоёв на карте. + +TP-05..TP-07, TP-09 (e2e) и TP-08 (integration) по своей природе не +покрываются юнит-тестом — это ожидаемо и не является замечанием к данному PR. + +## Findings + +### P3-1 (nice-to-have) — рассинхрон `toggleLayer('poi')` с чекбоксом + +`toggleLayer(group)` (`app.js:386-396`) меняет `layerState.poi`, но не +обновляет ни чекбокс `poi-visible-cb`, ни `localStorage`. ADR-0001 п.4 +указывает, что состояние кнопки и чекбокса не должно расходиться. + +Смягчающие факты: `toggleLayer` не имеет ни одного вызова в кодовой базе, +элемента `btn-poi` в `index.html` нет (вызов `toggleLayer('poi')` упал бы +на `btn.classList`). Фактически это недостижимый код, расхождение +пользователю не наблюдаемо. ADR сам относит унификацию тулбар-кнопок и +popup-чекбоксов к будущему техдолгу (раздел «Технический долг»). + +Рекомендация (вне scope ET-002): удалить мёртвый `toggleLayer` либо +оформить unified-контроллер слоёв отдельной задачей. + +### P3-2 (nice-to-have) — REQ-NF-03 не проверяется по диффу + +Touch target ≥ 44px зависит от CSS-класса `terrain-checkbox` в `app.css`, +который в рамках ET-002 не менялся (ТЗ §3 предписывает переиспользовать +существующий класс). Замечания к коду нет — отметка для приёмки: +подтвердить REQ-NF-03 прогоном e2e-теста TP-09. + +### P3-3 (nice-to-have) — неровный отступ в IIFE `initTerrain` + +В `app.js:2949-2950` и `2962-2963` вызовы `restoreTrailsState()` / +`restorePoiState()` имеют отступ, не совпадающий с окружающим блоком. +Это предсуществующая стилевая мелочь, которую ET-002 лишь продолжил. +Косметика; рекомендуется выровнять при ближайшем касании файла. + +## Итог + +Реализация полностью соответствует ТЗ и ADR-0001, код аккуратен, юнит-тесты +исполняют реальный код и покрывают TP-01..TP-04. Найдены только три +замечания уровня P3, ни одно из которых не блокирует мерж. + +**Вердикт: APPROVED.** diff --git a/docs/work-items/ET-002/12-review.md b/docs/work-items/ET-002/12-review.md new file mode 100644 index 0000000..dbe9064 --- /dev/null +++ b/docs/work-items/ET-002/12-review.md @@ -0,0 +1,168 @@ +--- +type: review +work_item_id: ET-002 +title: "Code Review: Чекбокс показа/скрытия POI в попапе рельефа" +version: 1 +status: approved +verdict: APPROVED +created_at: 2026-05-21 +authors: + - "agent:reviewer" +--- + +# Code Review — ET-002 + +## Вердикт + +**APPROVED** (с комментариями). + +P0/P1-findings нет. Зафиксировано 1×P2 и 3×P3 — все некритичные, не +блокируют merge. P2 относится к рассогласованию апстрим-артефактов +(test-plan vs infra-requirements), а не к дефекту реализации. + +## Объект ревью + +- Ветка: `feature/ET-002-poi-toggle` +- Базовая точка: `main` (832099c3) — ветка на 4 коммита впереди +- Код-коммит: `8c17a4f` `feat(web): add POI visibility checkbox to terrain popup` +- Изменённые файлы кода: + - `src/web/index.html` (+5 строк) + - `src/web/app.js` (+58 строк) + - `tests/unit/poi_toggle.test.js` (новый, 167 строк) + - `tests/unit/test_poi_toggle.py` (новый, 162 строки) +- Прочитано: `02-trz.md`, `03-acceptance-criteria.md`, `04-test-plan.yaml`, + `06-adr/adr-0001-poi-visibility-client-side.md`, `07-infra-requirements.md`, + `CLAUDE.md`. + +## 1. Соответствие ТЗ + +| Требование | Статус | Комментарий | +|------------|--------|-------------| +| REQ-F-01 Чекбокс в попапе после «Тропы», отделён `
` | ✅ | `index.html:56-60` — `
` + `label.terrain-checkbox`, идёт сразу после `trails-path-cb` | +| REQ-F-02 Состояние по умолчанию — checked | ✅ | Атрибут `checked` в HTML; `restorePoiState()` при отсутствии ключа даёт `poiOn = true` | +| REQ-F-03 Скрытие → `visibility: 'none'` обоих слоёв | ✅ | `applyPoiVisibility(false)` → `setLayoutProperty` для `layerGroups.poi` | +| REQ-F-04 Показ → `visibility: 'visible'` | ✅ | `applyPoiVisibility(true)` | +| REQ-F-05 Сохранение в `localStorage` `poi-visible` `'1'`/`'0'` | ✅ | `onPoiCheckbox()` → `localStorage.setItem('poi-visible', …)` | +| REQ-F-06 Восстановление при загрузке | ⚠️ | Реализовано; нештатные значения ключа трактуются как «скрыть» — см. R-02 (P3) | +| REQ-F-07 Синхронизация `layerState.poi` | ✅ | `applyPoiVisibility()` пишет `layerState.poi` первой строкой | +| REQ-NF-01 Производительность < 50 мс | ✅ | Только `setLayoutProperty`, тайлы не перезапрашиваются | +| REQ-NF-02 Совместимость браузеров | ✅ | Стандартные API (`localStorage`, `setLayoutProperty`) | +| REQ-NF-03 Мобильная доступность ≥ 44px | ✅ | Переиспользован общий класс `terrain-checkbox` (как у соседних чекбоксов) | +| REQ-NF-04 Отсутствие регрессий | ✅ | Изменения аддитивные; точки восстановления зеркалят `restoreTrailsState()` | + +Все функциональные и нефункциональные требования ТЗ выполнены. + +## 2. Соответствие ADR-0001 + +| Пункт решения ADR | Статус | Комментарий | +|-------------------|--------|-------------| +| п.1 Видимость через `setLayoutProperty` | ✅ | Подтверждено; `removeLayer`/`addLayer` не используются (Вариант C отвергнут корректно) | +| п.2 Персистентность `localStorage` `poi-visible` `'1'`/`'0'` | ✅ | Соответствует конвенции проекта | +| п.3 Источник истины — `layerState.poi`; `onPoiCheckbox()` синхронно обновляет state + storage + visibility | ✅ | Выполнено | +| п.4 Без дублирования; переиспользование `layerGroups.poi`; общий хелпер | ✅ | Создан `applyPoiVisibility(visible)` — буквально имя, предложенное в ADR; используется и `onPoiCheckbox()`, и `restorePoiState()` | +| п.4 Консистентность `toggleLayer('poi')` ↔ `onPoiCheckbox()` через `layerState.poi` | ✅ | Обе функции читают/пишут `layerState.poi`. POI-кнопки в тулбаре нет (`btn-poi` отсутствует, `toggleLayer` нигде не вызывается) — визуальный рассинхрон невозможен | +| п.5 Backend/БД/API/инфраструктура без изменений | ✅ | Затронут только `src/web/` | + +Реализация полностью соответствует принятому ADR. Отдельно отмечается +точное следование п.4 — выделен ровно тот приватный хелпер, который ADR +описывал как рекомендуемый. + +## 3. Acceptance Criteria + +| AC | Покрытие | +|----|----------| +| AC-01 Чекбокс в попапе после «Тропы», отделён линией | ✅ `test_poi_checkbox_placed_after_trails_separated_by_hr` | +| AC-02 POI включены по умолчанию | ✅ `test_poi_checkbox_checked_by_default`, JS `TP-04` | +| AC-03 Скрытие POI | ✅ JS `TP-01` | +| AC-04 Показ POI | ✅ JS `TP-02` | +| AC-05 Состояние сохраняется после перезагрузки | ✅ JS `TP-03` (restore при `poi-visible=0`) | +| AC-06 Восстановление включённого состояния | ✅ JS `restorePoiState() при poi-visible=1` | +| AC-07 Не ломает существующие чекбоксы | ✅ JS `onPoiCheckbox() меняет только poi-circles/poi-labels`; изменения HTML аддитивны | +| AC-08 Синхронизация с `layerState` | ✅ JS `TP-01`/`TP-02` (`layerState.poi`), `applyPoiVisibility()` без слоёв | + +Все 8 критериев имеют поведенческое покрытие. + +## 4. Качество кода + +Сильные стороны: + +- **Нет дублирования.** Логика видимости группы вынесена в единый + `applyPoiVisibility()`; карта слоёв `layerGroups.poi` не продублирована. +- **Консистентность с кодовой базой.** `restorePoiState()` подключён в + тех же трёх точках, что и `restoreTrailsState()` (`rebuildMapOverlays` + + обе ветки `initTerrain`), используется `window._map`, паттерн + `localStorage` `'1'`/`'0'` — всё единообразно с существующим кодом. +- **Защитное программирование.** Проверки `if (!map)`, `if (map.getLayer(id))`, + `if (cb)`; `layerState.poi` обновляется даже когда карта/слои ещё не + готовы (порядок инициализации безопасен). +- **Документированность.** JSDoc на всех трёх функциях, блок-маркеры с + ссылкой на ADR, запись в `CHANGELOG.md`. +- **Коммиты.** Conventional Commits соблюдён (`feat(web):`, `docs(ET-002):`). + +Замечания — см. findings R-02, R-03 (P3). + +## 5. Качество тестов + +- **Unit (`poi_toggle.test.js`)** — высокое качество: тесты исполняют + **реальный** код из `app.js` (блок извлекается по маркерам и + оборачивается через `new Function` с инъекцией моков), а не его копию. + Покрыты TP-01..TP-04 + 3 дополнительных кейса (значение `'1'`, + изоляция чужих слоёв, синхронизация state без слоёв на карте). +- **Python (`test_poi_toggle.py`)** — статические проверки структуры + HTML/JS (REQ-F-01, REQ-F-02, ADR-0001) + запуск JS-раннера через + `node --test`, со `skip` при отсутствии `node` (по аналогии с + существующим `test_lua_syntax`/`luac`). +- Браузерные e2e TP-05..TP-09 не реализованы — см. R-01 (P2). + +## Findings + +### R-01 — Отклонение от test-plan: e2e TP-05..TP-09 не реализованы (P2) + +`04-test-plan.yaml` определяет TP-05..TP-09 как `type: e2e`, однако +`07-infra-requirements.md` §6 явно запрещает новые npm-пакеты, а +Playwright-инфраструктуры в репозитории нет. Это конфликт между двумя +**approved**-артефактами, а не дефект разработчика: реализовать e2e «как +написано» означало бы нарушить инфра-требования. Поведение всех +сценариев покрыто статическими + unit-тестами, отклонение подробно +задокументировано в шапке `test_poi_toggle.py`. +**Рекомендация:** Analyst — согласовать `04-test-plan.yaml` с +`07-infra-requirements.md` (пометить TP-05..09 как покрытые альтернативно +либо завести отдельную инфра-задачу на Playwright). На merge ET-002 не +влияет. + +### R-02 — `restorePoiState()`: нештатное значение ключа скрывает POI (P3) + +`const poiOn = stored === null || stored === '1';` — любое значение, +отличное от `'1'`/`null` (например мусор в `localStorage`), трактуется +как «скрыть». REQ-F-06 описывает только `'0'` и `'1'`/`null`, а дефолт +фичи (REQ-F-02) — POI включены. Надёжнее: `const poiOn = stored !== '0';` +— тогда повреждённое значение деградирует к дефолту «показать». +Крайний кейс (ключ пишет только это приложение), отсюда P3. + +### R-03 — Непоследовательные отступы в `initTerrain` (P3) + +`app.js:2948-2950` и `2961-2963`: `restoreTrailsState()`/`restorePoiState()` +смещены относительно `restoreTerrainState()`. Дефект унаследован из +существующего кода — новые строки повторяют локальный (сломанный) отступ. +Косметика; при желании выровнять блок целиком. + +### R-04 — Хрупкая связанность тестов с исходником (P3) + +Тесты зависят от комментариев-маркеров `>>> ET-002 POI visibility block` +в `app.js` и от точного совпадения пробелов в +`test_poi_logic_reuses_layer_state_and_groups` +(`"poi: ['poi-circles', 'poi-labels']"`). Для монолитного non-module +`app.js` это прагматичное и задокументированное решение, но хрупкое при +рефакторинге/прогоне линтера. Технический долг — учесть при будущей +модуляризации фронтенда. + +## Заключение + +Реализация ET-002 корректна, полна и точно следует ТЗ и ADR-0001. +Дублирования нет, регрессионные риски закрыты повторением проверенного +паттерна `restoreTrailsState()`. Тесты исполняют реальный код и +покрывают все acceptance-критерии. Блокирующих замечаний нет. + +**Вердикт: APPROVED.** R-02/R-03/R-04 — на усмотрение разработчика +(можно поправить в этом же PR или вынести в техдолг). R-01 — действие на +стороне Analyst, merge не блокирует. diff --git a/docs/work-items/ET-002/13-test-report.md b/docs/work-items/ET-002/13-test-report.md new file mode 100644 index 0000000..53b82a2 --- /dev/null +++ b/docs/work-items/ET-002/13-test-report.md @@ -0,0 +1,227 @@ +--- +type: test-report +work_item_id: ET-002 +version: 1 +status: pass +tester: "agent:tester" +date: 2026-05-21 +commit_tested: 8c17a4f +verdict: PASS +--- + +# Test Report — ET-002 + +## Verdict: **PASS** → `stage:ready-to-deploy` + +Полный регресс зелёный: **14 passed, 4 skipped, 0 failed**, lint чистый, +JS-юнит-тесты POI 7/7 pass, тест-окружение отвечает 200. Блокирующих +багов (P0/P1) не найдено. + +Тест-кейсы TP-01..TP-04 и TP-08 из `04-test-plan.yaml` исполнены и +прошли. e2e-кейсы TP-05..TP-07, TP-09 **не исполнялись** — в репозитории +нет Playwright-инфраструктуры, а `07-infra-requirements.md §6` запрещает +новые npm-пакеты (конфликт двух approved-артефактов, зафиксирован +reviewer'ом как R-01/P2). Поведенческая суть этих сценариев покрыта +unit- и статическими тестами. На merge/деплой ET-002 не влияет. + +## Окружение + +- **Дата прогона:** 2026-05-21 +- **Ветка:** `feature/ET-002-poi-toggle` +- **Коммит:** `8c17a4f` (`feat(web): add POI visibility checkbox to terrain popup`; + взят из `12-review.md` — `git` в окружении тестера недоступен) +- **Python:** 3.12.13 +- **pytest:** 8.3.3 (plugins: asyncio-1.3.0, anyio-4.13.0) +- **Node:** v22.22.2 (`node --test`) +- **ruff:** 0.15.14 +- **test-env:** https://openclaw.mva154.duckdns.org/enduro/ → HTTP 200 + +## Healthcheck + +| Среда | URL | Код | +|---|---|---| +| local dev | http://localhost:5556/health | connection refused (dev не поднят — ОК, прогон оффлайн) | +| test | https://openclaw.mva154.duckdns.org/enduro/ | 200 | +| test API | https://openclaw.mva154.duckdns.org/enduro/api/health | 200 `{"status":"ok","db_exists":true}` | + +ET-002 — фронтенд-изменение; в test задеплоен `main`, поэтому healthcheck +подтверждает только живость окружения, а не наличие фичи (фича попадёт +в test штатной перевыкладкой после merge). + +## Команды запуска + +```bash +# Unit + integration (эквивалент make test) +python -m pytest tests/ -v + +# JS behavioral unit-тесты POI (TP-01..TP-04) +node --test tests/unit/poi_toggle.test.js + +# Lint +ruff check src/ +ruff check tests/ +``` + +## Результаты pytest + +`python -m pytest tests/ -v` → **14 passed, 4 skipped, 1 warning in 0.51s** + +| # | Тест | Тип | Результат | +|---|---|---|---| +| 1 | `test_routing_barriers.py::test_lua_syntax` | unit (структура lua) | **PASS** | +| 2 | `test_routing_barriers.py::test_blocked_barriers_match_trz` | static AC | **PASS** | +| 3 | `test_routing_barriers.py::test_excluded_highways_match_trz` | static AC | **PASS** | +| 4 | `test_routing_barriers.py::test_route_avoids_barrier` | integration | **SKIP** (OSRM недоступен) | +| 5 | `test_routing_barriers.py::test_route_no_footway` | integration | **SKIP** (OSRM недоступен) | +| 6 | `test_routing_barriers.py::test_route_allows_cattle_grid` | integration | **SKIP** (OSRM недоступен) | +| 7 | `test_routing_barriers.py::test_existing_route_works` | regression | **SKIP** (OSRM недоступен) | +| 8 | `test_health.py::test_health_endpoint` | unit (async) | **PASS** | +| 9 | `test_poi_toggle.py::test_poi_checkbox_present_in_html` | static (REQ-F-01) | **PASS** | +| 10 | `test_poi_toggle.py::test_poi_checkbox_checked_by_default` | static (REQ-F-02) | **PASS** | +| 11 | `test_poi_toggle.py::test_poi_checkbox_placed_after_trails_separated_by_hr` | static (REQ-F-01) | **PASS** | +| 12 | `test_poi_toggle.py::test_poi_checkbox_uses_shared_style_class` | static (UI-спец) | **PASS** | +| 13 | `test_poi_toggle.py::test_poi_functions_defined` | static (ADR-0001) | **PASS** | +| 14 | `test_poi_toggle.py::test_poi_logic_uses_localstorage_key` | static (REQ-F-05) | **PASS** | +| 15 | `test_poi_toggle.py::test_poi_logic_reuses_layer_state_and_groups` | static (ADR п.3-4) | **PASS** | +| 16 | `test_poi_toggle.py::test_restore_poi_state_wired_into_init` | static (REQ-F-06) | **PASS** | +| 17 | `test_poi_toggle.py::test_poi_visibility_toggled_via_set_layout_property` | static (ADR п.1) | **PASS** | +| 18 | `test_poi_toggle.py::test_js_unit_tests_pass` | behavioral (Node-раннер) | **PASS** | + +**4 SKIP** — интеграционные тесты роутинга ET-001; требуют поднятого OSRM +(`OSRM_URL`, по умолчанию `http://172.22.0.1:5559`), который в окружении +тестера недоступен. Это штатное поведение (`test_routing_barriers.py` +помечает их `skip`, чтобы CI без инфраструктуры не падал). ET-002 — +фронтенд-изменение, на роутинг влиять не может; к регрессу не относится. + +Предупреждение `PendingDeprecationWarning` из `starlette/formparsers.py` — +внешняя зависимость, к ET-002 отношения не имеет, не блокирует. + +## Результаты JS unit-тестов (TP-01..TP-04) + +`node --test tests/unit/poi_toggle.test.js` → **# pass 7, # fail 0** + +Тесты исполняют **реальный** код POI-блока из `src/web/app.js` (блок +извлекается по маркерам `ET-002 POI visibility block` и оборачивается +через `new Function` с инъекцией моков). + +| Тест | TC | Результат | +|---|---|---| +| снятый чекбокс скрывает слои POI и сохраняет `0` | TP-01 | **PASS** | +| установленный чекбокс показывает слои POI и сохраняет `1` | TP-02 | **PASS** | +| `restorePoiState()` при `poi-visible=0` скрывает POI | TP-03 | **PASS** | +| `restorePoiState()` без ключа включает POI по умолчанию | TP-04 | **PASS** | +| `restorePoiState()` при `poi-visible=1` показывает POI | доп. | **PASS** | +| `onPoiCheckbox()` меняет только `poi-circles`/`poi-labels` | доп. (дух TP-08) | **PASS** | +| `applyPoiVisibility()` синхронизирует `layerState` без слоёв | доп. | **PASS** | + +## Результаты lint + +| Команда | Результат | +|---|---| +| `ruff check src/` | **All checks passed!** | +| `ruff check tests/` | **All checks passed!** | + +## Покрытие тест-плана (04-test-plan.yaml) + +| TC | Тип | Исполнение | Статус | +|---|---|---|---| +| **TP-01** | unit | JS-тест «TP-01» (через `test_js_unit_tests_pass`) | **PASS** | +| **TP-02** | unit | JS-тест «TP-02» | **PASS** | +| **TP-03** | unit | JS-тест «TP-03» | **PASS** | +| **TP-04** | unit | JS-тест «TP-04» | **PASS** | +| **TP-05** | e2e | **BLOCKED** (нет Playwright). Покрыто статически: `test_poi_checkbox_present_in_html`, `test_poi_checkbox_placed_after_trails_separated_by_hr` | COVERED (alt) | +| **TP-06** | e2e | **BLOCKED** (нет Playwright). Покрыто поведенчески: JS «TP-01»/«TP-02», «меняет только poi-circles/poi-labels» | COVERED (alt) | +| **TP-07** | e2e | **BLOCKED** (нет Playwright). Покрыто поведенчески: JS «TP-03» (`restorePoiState` при `poi-visible=0`) | COVERED (alt) | +| **TP-08** | integration | JS «onPoiCheckbox() меняет только слои poi-circles и poi-labels» — изоляция чужих слоёв подтверждена | **PASS** (alt) | +| **TP-09** | e2e | **BLOCKED** (нет Playwright). Класс `terrain-checkbox` подтверждён статически; по touch-target см. P3-2 | PARTIAL | + +**Исполнено и пройдено: TP-01..TP-04, TP-08 (5/9).** +**e2e TP-05..TP-07, TP-09 (4/9) — заблокированы инфраструктурой**, их +поведение покрыто unit/статикой. Прямой инструментальной проверки в +реальном браузере не было. + +## Соответствие Acceptance Criteria + +| AC | Описание | Источник проверки | Статус | +|---|---|---|---| +| AC-01 | Чекбокс в попапе после «Тропы», отделён `
` | `test_poi_checkbox_placed_after_trails_separated_by_hr` | **PASS** | +| AC-02 | POI включены по умолчанию | `test_poi_checkbox_checked_by_default` + JS «TP-04» | **PASS** | +| AC-03 | Скрытие POI → `visibility: none` | JS «TP-01» | **PASS** | +| AC-04 | Показ POI → `visibility: visible` | JS «TP-02» | **PASS** | +| AC-05 | Состояние сохраняется после перезагрузки | JS «TP-03» (`restorePoiState` при `poi-visible=0`) | **PASS** | +| AC-06 | Восстановление включённого состояния | JS «restorePoiState при poi-visible=1» | **PASS** | +| AC-07 | Не ломает существующие чекбоксы | JS «меняет только poi-circles/poi-labels»; HTML-изменения аддитивны | **PASS** | +| AC-08 | Синхронизация с `layerState.poi` | JS «TP-01»/«TP-02» + «applyPoiVisibility без слоёв» | **PASS** | + +Все 8 критериев имеют поведенческое покрытие; ни один не нарушен. +Браузерная (e2e) проверка AC-01/03/04/05 ограничена отсутствием +Playwright — поведение подтверждено на уровне реального кода POI-блока. + +## Найденные баги + +### P0 (блокирующие) +Нет. + +### P1 (критические) +Нет. + +### P2 (важные) + +**T-01 (= R-01 из `12-review.md`) — e2e TP-05..TP-09 не исполнимы.** +`04-test-plan.yaml` определяет TP-05..TP-09 как `type: e2e`, но +`07-infra-requirements.md §6` запрещает новые npm-пакеты, а +Playwright-инфраструктуры в репозитории нет (проверено: нет +`package.json`, `node_modules`, бинаря `playwright`, `npx`). Это конфликт +двух **approved**-артефактов, а не дефект разработки. Поведение покрыто +unit/статикой. +**Действие:** Analyst — согласовать `04-test-plan.yaml` с +`07-infra-requirements.md` (пометить TP-05..09 как покрытые альтернативно +либо завести инфра-задачу на Playwright). **Merge/деплой ET-002 не +блокирует.** + +### P3 (косметика / наблюдения) + +1. **(= R-02 из `12-review.md`)** `app.js:2849` — + `const poiOn = stored === null || stored === '1';`: нештатное значение + ключа `poi-visible` трактуется как «скрыть». Надёжнее + `stored !== '0'` (деградация к дефолту «показать», REQ-F-02). + Крайний кейс — ключ пишет только это приложение. Не блокирует. +2. **REQ-NF-03 (touch target ≥ 44px)** — `.terrain-checkbox` + (`app.css:806`) при `padding:8px` и `font-size:15px` даёт высоту + строки ≈ 35px, что ниже ориентира 44px. Однако чекбокс POI + использует **тот же** класс, что и все соседние чекбоксы попапа + (Тени/Перепады/Грунтовки/Тропы) — **регрессии нет**, кликабельна вся + строка-`