From eb9adbc93003ac3d1d1c73779f1a81700201c440 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Thu, 4 Jun 2026 06:00:55 +0000 Subject: [PATCH] analyst(ET): auto-commit from analyst run_id=72 --- docs/work-items/ET-012/01-brd.md | 216 +++++++++ docs/work-items/ET-012/02-trz.md | 442 ++++++++++++++++++ .../ET-012/03-acceptance-criteria.md | 214 +++++++++ docs/work-items/ET-012/04-test-plan.yaml | 401 ++++++++++++++++ docs/work-items/ET-012/04b-ui-test-cases.md | 375 +++++++++++++++ 5 files changed, 1648 insertions(+) create mode 100644 docs/work-items/ET-012/01-brd.md create mode 100644 docs/work-items/ET-012/02-trz.md create mode 100644 docs/work-items/ET-012/03-acceptance-criteria.md create mode 100644 docs/work-items/ET-012/04-test-plan.yaml create mode 100644 docs/work-items/ET-012/04b-ui-test-cases.md diff --git a/docs/work-items/ET-012/01-brd.md b/docs/work-items/ET-012/01-brd.md new file mode 100644 index 0000000..345b397 --- /dev/null +++ b/docs/work-items/ET-012/01-brd.md @@ -0,0 +1,216 @@ +--- +type: brd +work_item_id: ET-012 +title: "BRD: Показывать пользовательские треки с зума z5 (сейчас с z8)" +version: 1 +status: draft +created_at: 2026-06-04 +updated_at: 2026-06-04 +authors: + - "agent:analyst" +related: + - "ET-008" + - "ET-009" +--- + +# BRD — ET-012: Показывать пользовательские треки с зума z5 + +## 1. Цель + +Снизить нижний порог видимости слоя публичных GPS-треков +(`gps-tracks-layer-mvt`) с **z8** до **z5**, чтобы пользователь +видел общее покрытие сети треков на средних/мелких масштабах +(z5 ≈ ¼ Европы в кадре, z7 ≈ область размером с ЦФО) и мог +«с высоты птичьего полёта» искать интересные треки. + +На сегодня (после ET-008/ET-009) слой публичных треков физически +скрыт ниже z8 двумя механизмами: + +- vector-source задаёт `minzoom: 8` (тайлы не запрашиваются); +- клиентский visibility-фильтр `zoom >= GPS_TRACKS_MIN_ZOOM` (8) + в `_syncGpsLayersVisibility` и `applyGpsHaloVisibility`; +- UI-hint «Зум 8+» (`#public-tracks-zoom-hint`) висит как + обоснование «почему пусто». + +ET-012 = **снизить порог + сохранить читаемость и +производительность** на новых зумах z5-z7. + +## 2. Контекст + +### 2.1 Текущее поведение (после ET-009) + +- Источник `gps-tracks-tiles` (MVT): + `tiles: /api/gps-tracks/tiles/{z}/{x}/{y}.mvt`, + `minzoom: 8`, `maxzoom: 11`. +- Источник `gps-tracks-geo` (GeoJSON, через `/api/gps-tracks?bbox=…`) + включается при `zoom >= GPS_TRACKS_ZOOM_CUTOFF = 12` — + ET-012 в этом регионе ничего не меняет. +- `build_gps_mvt` (`src/api/gps_tracks/mvt.py`) уже содержит + zoom-aware упрощение и пороги: + - `_simplify_coords`: tolerance `0.008°` (~800м) на z≤7, + `0.002°` (~200м) на z8-9, `0.0005°` (~50м) на z10-11, + без упрощения на z≥12. + - В `build_gps_mvt`: при z≤7 — `min_length_m=2000`, `limit=3000` + features на тайл; на больших зумах limit/min_length мягче. +- Endpoint `/api/gps-tracks/tiles/{z}/{x}/{y}.mvt` принимает + любой `0 ≤ z ≤ 22`; никакой пре-нарезки тайлов нет — + каждый тайл строится из БД on-demand и кэшируется в FIFO + размером 1024. +- На клиенте используется LRU-кэш MapLibre и сетевой кэш браузера. +- Текущая БД (test-среда) содержит порядка нескольких сотен + треков (ожидаемо ≤ 5000 в горизонте года), геометрия каждого + трека — десятки-тысячи точек. + +### 2.2 Почему это бизнес-важно + +- На малых масштабах (z5-z7) пользователю **сейчас негде искать + треки**: при первом открытии карта по умолчанию показывает + обзор региона; чтобы увидеть хоть что-то из публичных треков, + нужно сразу зумить до z8 — это лишний шаг и плохой UX. +- Видимость на z5-z7 = понимание «где вообще катаются» в + масштабах целого региона/страны, что помогает планировать + выезды и оценивать покрытие. +- Конкуренты (Wikiloc, Komoot) показывают clustered/density + слои с z3-z4; для нас достаточно начать с z5. + +### 2.3 Открытые вопросы из бизнес-запроса — ответы по результатам анализа + +| Вопрос | Ответ | +| ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| Где задаётся minzoom слоя? | Клиент: `src/web/gps_tracks.js`, константа `GPS_TRACKS_MIN_ZOOM = 8` (используется в source.minzoom, visibility, halo, hint). | +| Тайлы уже нарезаны до z5 или нужно догенерить? | Нарезки нет вообще — тайлы строятся on-demand из SQLite по bbox. Никакой генерации/инвалидации делать не нужно. | +| Нужна ли генерализация линий на малых зумах? | Базовая уже есть в `_simplify_coords` (DP-tolerance 800м при z≤7). Для z5-z6 нужно ужесточить пороги (min_length, limit, tolerance) — F-04..F-06. | + +## 3. Scope + +### In scope + +| # | Функция | +| ----- | -------------------------------------------------------------------------------------------------------------------- | +| F-01 | Снизить клиентскую константу `GPS_TRACKS_MIN_ZOOM` с 8 до 5 в `src/web/gps_tracks.js`. | +| F-02 | Уменьшить `minzoom` vector-source `gps-tracks-tiles` с 8 до 5 (использует ту же константу). | +| F-03 | На бэкенде в `build_gps_mvt` расширить tier-структуру: добавить уровни z5, z6 с более жёсткими min_length_m и limit. | +| F-04 | В `_simplify_coords` добавить tier для z5-z6: tolerance ~0.02° (~2 км) на z5, ~0.01° (~1 км) на z6. | +| F-05 | Расширить `line-width` (основной слой) и `line-width` (halo) для z5: явные stops чтобы линия читалась. | +| F-06 | UI-hint `#public-tracks-zoom-hint`: либо обновить текст до «Зум 5+», либо скрывать всегда (после снижения порога порог фактически недостижим в обычных сценариях). | +| F-07 | Halo на спутнике активируется на z5-z11 (как и основной MVT-слой). | +| F-08 | Производительность: p95 generation одного MVT-тайла z5 ≤ 500 мс при размере БД ≤ 5000 треков; p95 endpoint не выше +50 мс относительно baseline ET-009. | +| F-09 | Читаемость: на z5 с включённым слоем при ≥ 200 треках по ЦФО карта остаётся «читаемой» — линии не сливаются в сплошную кашу. Критерий приёмки качественный, см. AC-08. | +| F-10 | Halo на спутнике на z5-z7: не «глушит» подложку. Halo-width на z5 ≤ 2 px. | +| F-11 | Регрессия: поведение на z8-z11 (MVT) и z12+ (GeoJSON) не меняется. | +| F-12 | Регрессия: фильтры по `activity` / `source` работают на z5-z7 так же, как на z8+. | +| F-13 | Регрессия: popup трека и кнопка «Скачать GPX» (ET-011) работают при клике на трек на z5-z7. (Замечание: на низких зумах кликнуть в линию пальцем сложнее — оставляем как есть, hit-area не расширяем.) | + +### Out of scope + +- **Clustering / heat-map на z3-z4.** Идея здравая, но требует + отдельной серверной агрегации (например, h3-cell counts) и + нового UI-слоя. Делаем отдельным work item. +- **Пре-нарезка тайлов на диск.** Не требуется при текущем + размере БД; on-demand + LRU справляются. +- **Изменение поведения GeoJSON-слоя на z12+.** Не трогаем. +- **Изменение фильтров по activity/source.** Не трогаем. +- **Изменения popup'а трека.** Не трогаем. +- **Расширение `gps_tracks_minzoom` в админ-конфиг.** Константа + остаётся хардкодом — менять через релиз. Если в будущем + появится потребность в feature-flag — отдельный work item. +- **Изменения схемы БД и dedup-алгоритма.** Не трогаем. +- **Изменения OSRM / routing.** Не трогаем. + +## 4. Метрики успеха + +| # | Метрика | Критерий | +| --- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| M-1 | Видимость на z5 | При включённом чекбоксе «Публичные треки» и `zoom = 5` слой `gps-tracks-layer-mvt` имеет `visibility: visible`. На карте отображаются линии треков. | +| M-2 | Видимость на z6, z7 | Аналогично M-1 для z6 и z7. | +| M-3 | Поведение на z8-z11 не изменилось | Регрессия: на z8-z11 виден тот же набор треков, что и до ET-012 (с поправкой на улучшенную z5-z7 генерализацию — не считается регрессией). | +| M-4 | Поведение на z12+ не изменилось | Регрессия: GeoJSON-слой включается ровно на z=12 как раньше; MVT слой скрывается ровно на z=12. | +| M-5 | Hint «Зум 5+» / «Зум 8+» | После ET-012: при включённом слое и zoom ≥ 5 hint скрыт. (До ET-012 hint показывал «Зум 8+» при zoom < 8.) | +| M-6 | p95 MVT tile generation на z5 | ≤ 500 мс на test-среде при размере БД до 5000 треков; ≤ 1 с при размере до 20 000 треков (запас). | +| M-7 | p95 endpoint `/api/gps-tracks/tiles/5/x/y.mvt` | cold ≤ 700 мс, hit ≤ 50 мс (кэш). | +| M-8 | Размер MVT тайла z5 | ≤ 200 KB после генерализации и фильтра min_length (защита от мобильного трафика). Если больше — F-03/F-04 переусиливают (ужесточить limit). | +| M-9 | Читаемость z5 | На скриншоте z5 с ≥ 200 треков по ЦФО видны минимум 3 разных линии в разных частях кадра; нет «сплошной заливки» одной зоны. Качественная проверка по TC-UI-12-Z5-Q. | +| M-10 | Регрессия фильтров | Снятие галки «EnduroRussia» в `#sheet-gps-filters` на z=6 убирает соответствующие линии (как и на z=10). | +| M-11 | LRU-кэш не переполняется ненужно | После панорамирования по миру на z5-z6 (≈ 50 уникальных тайлов) кэш-хит на повторных тайлах ≥ 80 %. | + +## 5. Риски + +| # | Риск | Вероятность | Влияние | Митигация | +| ---- | ------------------------------------------------------------------------------------------ | ----------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| R-1 | На z5 слишком много фич в одном тайле → MVT > 1 MB, тормоза рендера на мобильном. | Средняя | Высокое | F-03: жёсткий `min_length_m` и `limit` для z=5. Метрика M-8 (≤ 200 KB) — гейт. При нарушении — ужесточить limit/min_length. | +| R-2 | На z5 линии после Douglas-Peucker превращаются в «обрубки» (трек из 1000 точек → 3 точки). | Средняя | Низкое | Качественная проверка по TC-UI-12-Z5-Q. Tolerance подобрана так, чтобы трек ≤ 5 км превращался в прямую — это норма на z5. | +| R-3 | Линия `line-width: 0.5 px` на z5 невидима на retina-дисплеях. | Низкая | Низкое | F-05: явные stops `interpolate linear zoom 5 0.8 8 1.0 12 2.0 16 3.0`. Проверка по TC-UI-01-Z5. | +| R-4 | Бэкенд-запрос к БД с огромным bbox (z5 тайл ~1250×1250 км) тянет ВСЕ треки региона. | Средняя | Среднее | Запрос уже идёт через индекс по min_lon/max_lon/min_lat/max_lat в SQLite; при ≤ 5000 строк это < 100 мс. M-7 — гейт. При деградации — добавить индекс `length_m`. | +| R-5 | На z5 buffer 10 % bbox в endpoint раздувает запрос до 130 % площади. | Низкая | Низкое | На z5 это уже не имеет смысла (соседний тайл всё равно отрисует пограничные фичи). Опционально — снизить buffer до 5 % для z≤6. См. TRZ §3.10. | +| R-6 | LRU-кэш в 1024 тайла на z5 (всего 32×32 = 1024 тайла в мире) — теоретически переполняется на «walk through world». | Низкая | Низкое | На практике пользователь видит ~10-20 тайлов одновременно на z5; ротация работает. Опционально — увеличить `_GPS_TILE_CACHE_MAX` до 2048. См. TRZ §3.11. | +| R-7 | Hint «Зум 8+» забыли удалить → пользователь видит и линии, и подсказку «увеличь зум». | Средняя | Низкое | F-06 явно: либо hide-always при `GPS_TRACKS_MIN_ZOOM = 5`, либо текст «Зум 5+». См. AC-05. | +| R-8 | Регрессия halo на спутнике: halo на z5 «закрывает» линию. | Низкая | Низкое | F-10: halo-width ≤ 2 px на z5; проверка по TC-UI-09-Z5-SAT. | +| R-9 | Пользователи на мобильном с медленным интернетом получают раздутые тайлы z5-z6 при первом открытии. | Средняя | Среднее | Размер ≤ 200 KB (M-8) + gzip на nginx + браузерный кэш. Опционально — отсрочить включение слоя до первого panMove (не в scope ET-012). | +| R-10 | Конфликт с поведением другого слоя `gps-tracks-halo-mvt-satellite`: оба используют те же фичи MVT — на z5 halo и линия должны быть согласованы. | Низкая | Низкое | Используют тот же source/source-layer; видимость синхронизируется через `_syncGpsLayersVisibility` + `applyGpsHaloVisibility`. Регрессионная проверка TC-UI-09-Z5-SAT. | + +## 6. Зависимости + +### Backend + +- `src/api/gps_tracks/mvt.py:build_gps_mvt` — расширить tier-таблицу + для z5, z6 (F-03). +- `src/api/gps_tracks/mvt.py:_simplify_coords` — добавить tier для z5-z6 (F-04). +- `src/api/gps_tracks/endpoint.py` — без изменений логики, опциональная + правка buffer для z≤6 (R-5). По умолчанию не меняем. +- Endpoint `/api/gps-tracks/tiles/{z}/{x}/{y}.mvt` уже принимает z 0..22 — не трогаем. + +### Frontend + +- `src/web/gps_tracks.js`: + - константа `GPS_TRACKS_MIN_ZOOM = 5` (F-01, F-02). + - `_gpsLayerDef` paint.line-width — расширить interpolate-выражение + для z5 (F-05). + - `_gpsHaloDef` paint.line-width — то же (F-05, F-10). +- `src/web/index.html`: + - `#public-tracks-zoom-hint` — обновить текст или скрыть навсегда (F-06). +- Стили `style.json` / `style-dark.json` — без изменений + (минзум слоя в стилях не задаётся; он живёт в коде клиента). + +### Тесты + +- Новые unit-тесты `tests/unit/test_gps_mvt_zoom_tiers.py` (новый файл): + тиры min_length и limit для z=5..z=12. +- Новые unit-тесты `tests/unit/test_gps_mvt_simplify.py` или расширение + существующих: tolerance для z5-z6. +- Новые integration-тесты `tests/integration/test_gps_tile_z5_z7.py`: + endpoint отдаёт непустой MVT для z=5/6/7 на регионе с ≥ 10 треками. +- UI-тесты см. `04b-ui-test-cases.md`. + +### Документация + +- `01-brd.md` (этот файл). +- `02-trz.md`, `03-acceptance-criteria.md`, `04-test-plan.yaml`, `04b-ui-test-cases.md` — этот пакет. +- Опциональный ADR не требуется: tile-pipeline уже спроектирован + под динамические тиры в ET-008/ET-009; это calibration, а не + архитектурное решение. Если разработчик в реализации обнаружит + нужду в смене политики (например, переход к heat-map на z5) — + добавляет ADR в `06-adr/`. + +### Инфра / Данные + +- Test-среда `https://openclaw.mva154.duckdns.org/enduro/` — + существующий деплой. +- БД `data/gps_tracks.sqlite` — без миграций. +- nginx gzip уже включён. + +### Связи с другими work items + +- **ET-008** — родительский слой публичных GPS-треков. +- **ET-009** — заполнил БД треками EnduroRussia/Wikiloc; без этих + данных z5-z7 будет визуально пустым в test-среде. +- **ET-011** — кнопка «Скачать GPX» в popup'е; регрессия покрывается. +- **PH-3 Smart Route** — независимо. +- Будущий work item «Heat-map / clustering на z3-z4» — отдельная задача. + +## 7. План в одну строку + +Снижаем константу `GPS_TRACKS_MIN_ZOOM` с 8 до 5, расширяем +zoom-tier структуру в `build_gps_mvt` и `_simplify_coords` для z5-z6, +добавляем явные line-width stops для z5, скрываем/обновляем hint, +гарантируем читаемость и производительность тестами и +скриншот-тестами. diff --git a/docs/work-items/ET-012/02-trz.md b/docs/work-items/ET-012/02-trz.md new file mode 100644 index 0000000..abfdbc2 --- /dev/null +++ b/docs/work-items/ET-012/02-trz.md @@ -0,0 +1,442 @@ +--- +type: trz +work_item_id: ET-012 +title: "ТЗ: Показывать пользовательские треки с зума z5" +version: 1 +status: draft +created_at: 2026-06-04 +updated_at: 2026-06-04 +authors: + - "agent:analyst" +related: + - "ET-008" + - "ET-009" +--- + +# ТЗ — ET-012: Показывать пользовательские треки с зума z5 + +## 1. Терминология + +- **MVT-слой** — `gps-tracks-layer-mvt`, отрисовка треков из + vector-source `gps-tracks-tiles` (тайлы `/api/gps-tracks/tiles/{z}/{x}/{y}.mvt`). + Активен при `GPS_TRACKS_MIN_ZOOM ≤ zoom < GPS_TRACKS_ZOOM_CUTOFF`. +- **GeoJSON-слой** — `gps-tracks-layer-geo`, отрисовка треков из + GeoJSON-source (запрос `/api/gps-tracks?bbox=…`). Активен при + `zoom ≥ GPS_TRACKS_ZOOM_CUTOFF = 12`. **ET-012 не трогает этот слой.** +- **Halo** — белый ореол на спутниковой подложке + (`gps-tracks-halo-mvt-satellite`, `gps-tracks-halo-geo-satellite`). +- **Zoom-tier** — диапазон зумов (например, `z ≤ 5`, `6 ≤ z ≤ 7`), + для которого `build_gps_mvt` применяет общий набор лимитов + (`min_length_m`, `limit`) и порог упрощения (`tolerance`). +- **Douglas-Peucker tolerance** — параметр `shapely.LineString.simplify`, + в градусах WGS84. На широте 55°: 1° долготы ≈ 64 км, 1° широты ≈ 111 км. +- **Zoom-hint** — UI-надпись «Зум 8+» (`#public-tracks-zoom-hint`), + подсказывающая, что нужно увеличить зум, чтобы увидеть слой. + +## 2. Архитектурные опоры + +ET-012 не строит новых модулей. Используем существующее: + +- `src/web/gps_tracks.js` — клиентский слой ET-008/ET-009/ET-011. + Константы: `GPS_TRACKS_ZOOM_CUTOFF = 12`, `GPS_TRACKS_MIN_ZOOM = 8`. +- `src/api/gps_tracks/mvt.py:build_gps_mvt` — серверная сборка MVT + с tier-логикой `min_length_m` / `limit` и `_simplify_coords`. +- `src/api/gps_tracks/endpoint.py:get_gps_tile` — обработчик + `/api/gps-tracks/tiles/{z}/{x}/{y}.mvt`. Валидация `0 ≤ z ≤ 22` + уже есть. LRU-кэш `_gps_tile_cache` размер 1024 — не меняем. +- `src/api/gps_tracks/db.py:get_tracks_in_bbox` — bbox-запрос + по индексам min_lon/max_lon/min_lat/max_lat. Не меняем. + +ET-012 = **значения констант + одна функция-tier + одна функция-simplify + три CSS/MapLibre-выражения + один hint**. + +## 3. Требования + +### REQ-F-01 — Клиентская константа `GPS_TRACKS_MIN_ZOOM` + +Файл `src/web/gps_tracks.js`, строка +```js +const GPS_TRACKS_MIN_ZOOM = 8; // ниже — слой скрыт +``` +заменить на +```js +const GPS_TRACKS_MIN_ZOOM = 5; // ниже — слой скрыт +``` + +**Acceptance check.** +```bash +grep -n "GPS_TRACKS_MIN_ZOOM" src/web/gps_tracks.js +``` +Первое вхождение содержит `= 5`. Никаких других мест объявления этой +константы в `src/web/` нет (`grep -R "GPS_TRACKS_MIN_ZOOM" src/web/`). + +### REQ-F-02 — Vector-source minzoom использует ту же константу + +В `_ensureGpsSources` (gps_tracks.js, около строки 178) запись +```js +minzoom: GPS_TRACKS_MIN_ZOOM, +``` +**не меняется** — она автоматически примет новое значение 5. + +**Acceptance check.** Через DevTools на test-среде: +```js +window._map.getSource('gps-tracks-tiles').minzoom === 5 +``` + +### REQ-F-03 — Backend: zoom-tier для z=5 и z=6 в `build_gps_mvt` + +Файл `src/api/gps_tracks/mvt.py`, функция `build_gps_mvt`, +блок «Min-length фильтр по зуму» (строки ~104-116) заменить на: + +```python +# Min-length фильтр и cap на число фич по зуму +if z <= 5: + min_length_m = 10000 # 10 км — только «магистральные» треки + limit = 1500 +elif z == 6: + min_length_m = 5000 # 5 км + limit = 2000 +elif z == 7: + min_length_m = 2000 # как было для z<=7 + limit = 3000 +elif z <= 9: + min_length_m = 0 + limit = 8000 +elif z <= 11: + min_length_m = 0 + limit = 15000 +else: + min_length_m = 0 + limit = 25000 +``` + +Цифры подобраны под цели: +- z5: лимит 1500 фич × ~200 байт после генерализации ≈ 300 KB MVT + до gzip — близко к гейту M-8 (200 KB). Если на реальных данных + получится > 200 KB — снизить `limit` до 1000 в дев-итерации. +- min_length 10 км отсекает короткие тестовые трассы — они + визуально не различимы на z5. + +### REQ-F-04 — Backend: tier для tolerance в `_simplify_coords` + +Файл `src/api/gps_tracks/mvt.py`, функция `_simplify_coords`, +заменить блок выбора tolerance на: + +```python +if z >= 12: + return coords +elif z >= 10: + tolerance = 0.0005 # ~50 м +elif z >= 8: + tolerance = 0.002 # ~200 м +elif z == 7: + tolerance = 0.008 # ~800 м (как сейчас для z<=7) +elif z == 6: + tolerance = 0.018 # ~2 км +else: + tolerance = 0.04 # ~4 км (z5 и ниже) +``` + +Замечание. `tolerance` — в градусах долготы; на 55° с.ш. её +эквивалент по расстоянию = `tolerance * 64 км`. Для z5 на пиксель +карты приходится ≈ 5 км по долготе на 55° с.ш., так что 4 км +tolerance даёт «1 точка на пиксель» — оптимум. + +### REQ-F-05 — Frontend: line-width для основного MVT-слоя на z5 + +Файл `src/web/gps_tracks.js`, функция `_gpsLayerDef`, выражение +`line-width`: +```js +'line-width': ['interpolate', ['linear'], ['zoom'], 8, 1.0, 12, 2.0, 16, 3.0], +``` +заменить на +```js +'line-width': ['interpolate', ['linear'], ['zoom'], + 5, 0.8, + 8, 1.0, + 12, 2.0, + 16, 3.0], +``` + +Stop на z5 = 0.8 px подобран так, чтобы на 1× и 2×-DPR дисплеях +линия гарантированно занимала ≥ 1 физический пиксель (с округлением +GPU). На retina (3×) — 2.4 пикселя, видимо. + +### REQ-F-06 — Frontend: line-width для halo на z5 + +Файл `src/web/gps_tracks.js`, функция `_gpsHaloDef`, выражение +`line-width`: +```js +'line-width': ['interpolate', ['linear'], ['zoom'], 8, 2.5, 12, 4.0, 16, 6.0], +``` +заменить на +```js +'line-width': ['interpolate', ['linear'], ['zoom'], + 5, 1.8, + 8, 2.5, + 12, 4.0, + 16, 6.0], +``` + +Halo на z5 = 1.8 px — белый ореол не должен «съедать» линию +толщиной 0.8 px. Соотношение ~2.25× оставляет халобакс по 0.5 px с каждой стороны. + +### REQ-F-07 — Frontend: zoom-hint «Зум 5+» + +Файл `src/web/index.html`, строка +```html + +``` +заменить на +```html + +``` + +В `_syncGpsLayersVisibility` (gps_tracks.js, строка ~358-362) логика +```js +hint.style.display = (enabled && zoom < GPS_TRACKS_MIN_ZOOM) ? 'inline' : 'none'; +``` +**не меняется** — она автоматически подхватит новый порог. + +**Замечание.** При z < 5 (фактически только z=0..4) hint всё ещё +появится, что и желательно: у пользователя есть подсказка, в каких +случаях линий нет «по дизайну». + +### REQ-F-08 — Endpoint без изменений + +`src/api/gps_tracks/endpoint.py:get_gps_tile` остаётся прежним: + +- Валидация `0 ≤ z ≤ 22` уже корректно пропускает z=5..7. +- Buffer 10 % bbox остаётся (для z≤6 это формально излишне, + но не вредит — соседние тайлы кэшируются независимо). +- LRU-кэш `_gps_tile_cache` размером 1024 остаётся. + +Никаких новых query-параметров не вводится. Никаких изменений +в `/api/gps-tracks?bbox=…` (GeoJSON endpoint) не делаем — +z12+ не затрагивается. + +### REQ-F-09 — Unit-тесты zoom-tier в `build_gps_mvt` + +Файл `tests/unit/test_gps_mvt_zoom_tiers.py` (новый или расширение +существующего `test_gps_mvt.py`): + +- **UT-Z5-01.** При z=5 и 10 треках, из которых 3 короче 10 км, в + итоговом MVT — ≤ 7 features. +- **UT-Z5-02.** При z=5 и 2000 треках длиннее 10 км — в MVT не + больше `limit=1500` features. +- **UT-Z6-01.** При z=6 и треках 3 км и 6 км — в MVT попадает + только трек 6 км. +- **UT-Z6-02.** При z=6 и 2500 треках длиной ≥ 5 км — в MVT + не больше 2000 features. +- **UT-Z7-01.** При z=7 поведение совпадает с прежним + (min_length=2000, limit=3000). Регрессия. +- **UT-Z8-01.** При z=8 поведение совпадает с прежним + (min_length=0, limit=8000). Регрессия. +- **UT-Z12-01.** При z=12 поведение совпадает с прежним + (limit=25000). Регрессия. + +### REQ-F-10 — Unit-тесты `_simplify_coords` для новых тиров + +Файл `tests/unit/test_gps_mvt_simplify.py` (новый или расширение): + +- **UT-SIMP-Z5-01.** Прямой трек 100 точек, диапазон ≈ 0.1° по широте/долготе: + при z=5 — возвращает ≤ 5 точек (DP с большим tolerance + схлопывает почти прямую). +- **UT-SIMP-Z5-02.** Зигзаг 100 точек, амплитуда зигзагов 0.01° + (≈ 1 км): при z=5 (tolerance ~4 км) — возвращает 2 точки + (зигзаги меньше tolerance, остаются только концы). +- **UT-SIMP-Z6-01.** Тот же зигзаг 100 точек, амплитуда 0.05° (~5 км): + при z=6 (tolerance ~2 км) — возвращает > 5 точек (видны + крупные зигзаги). +- **UT-SIMP-Z7-01.** Регрессия: при z=7 tolerance = 0.008, + поведение прежнее. +- **UT-SIMP-Z10-01.** Регрессия: при z=10 tolerance = 0.0005, + поведение прежнее. +- **UT-SIMP-Z12-01.** Регрессия: при z=12 функция возвращает + оригинальный coords без изменений. + +### REQ-F-11 — Integration-тесты endpoint z5-z7 + +Файл `tests/integration/test_gps_tile_z5_z7.py` (новый): + +- **IT-Z5-01.** На тестовой БД с 50 треками ≥ 10 км по ЦФО + запрос `GET /api/gps-tracks/tiles/5/19/9.mvt` (тайл, накрывающий + Москву): возвращает 200, Content-Type `application/x-protobuf`, + тело длиной > 0 и < 200 KB (M-8). +- **IT-Z5-02.** Размер MVT для того же тайла на БД из 200 треков + ≥ 10 км — ≤ 200 KB. +- **IT-Z5-03.** Тайл z=5 за пределами региона (например, центр + Тихого океана `tiles/5/4/12.mvt`): тело пустое, ответ 200. +- **IT-Z6-01.** Тайл z=6 над Москвой: размер < 200 KB, + features > IT-Z5-01. +- **IT-Z7-01.** Тайл z=7 над Москвой: features > IT-Z6-01 (более + мелкие треки попадают в фильтр), но всё ещё < `limit=3000`. +- **IT-CACHE-01.** Два подряд запроса одного тайла z=5: второй + возвращает заголовок `X-Cache: HIT`. + +### REQ-F-12 — Регрессионный тест: контракт endpoint не сломался + +- **IT-REGRESS-Z8-01.** Endpoint `/api/gps-tracks/tiles/8/x/y.mvt` + возвращает тот же набор треков, что и до ET-012 (sanity-check + через сравнение `mapbox_vector_tile.decode(body)['gps_tracks']['features']` + до и после; допустимо различие только в порядке). +- **IT-REGRESS-Z10-01.** Аналогично для z=10. + +### REQ-F-13 — Производительность: бенчмарк MVT z5 + +Файл `tests/performance/test_gps_mvt_z5_perf.py` (новый, +помечается маркером `@pytest.mark.perf`): + +- **PERF-Z5-01.** При тестовой БД из 500 треков по ЦФО и + 10 повторных вызовах `build_gps_mvt(rows, 5, 19, 9)`: + - среднее время выполнения ≤ 200 мс на CI-runner. + - 95-й перцентиль ≤ 500 мс (метрика M-6). + +Запуск отдельный (`pytest -m perf`), не в основной CI-gate. +Цель — раз-в-релиз проверять, что мы не уплыли. + +### REQ-F-14 — UI-тесты (Playwright) + +См. `04b-ui-test-cases.md`. Ключевые проверки: + +- TC-UI-01-Z5: при `zoom = 5` слой виден. +- TC-UI-02-Z6: при `zoom = 6` слой виден. +- TC-UI-03-Z7: при `zoom = 7` слой виден. +- TC-UI-04-HINT-OFF: hint «Зум 5+» **не** показывается при `zoom ≥ 5`. +- TC-UI-05-HINT-ON: hint показывается при `zoom < 5`. +- TC-UI-06-FILTER-Z6: фильтр источников работает на z6 (регрессия). +- TC-UI-07-POPUP-Z6: клик по треку на z6 открывает popup. +- TC-UI-08-Z11-REGRESS: на z11 слой по-прежнему виден (регрессия). +- TC-UI-09-Z12-CUTOFF: на z12 MVT-слой скрыт, GeoJSON-слой виден. +- TC-UI-10-Z5-MOBILE: на мобильном при z5 слой виден. +- TC-UI-11-Z5-SAT: на z5 со спутниковой подложкой halo не «глушит» подложку. +- TC-UI-12-Z5-Q: качественная проверка читаемости на z5. + +### REQ-F-15 — Не менять контракт `/api/gps-tracks*` + +Никаких новых query-параметров, заголовков, кодов ответа, +полей в JSON. `/health` endpoint не меняется. + +### REQ-F-16 — Не менять конфиги + +`config/gps_sources.yaml`, `config/gps_regions.yaml`, +миграции БД — без изменений. + +### REQ-F-17 — Не менять стили карты + +`src/web/style.json` и `src/web/style-dark.json` — без изменений. +Color-by-source / color-by-activity match-expressions внутри +`_buildColorExpression` в коде клиента — без изменений (треки +на z5-z7 будут окрашены теми же цветами). + +### REQ-F-18 — localStorage без миграции + +Текущий слой использует ключи `gps-tracks-enabled`, +`gps-tracks-activities`, `gps-tracks-sources`, `gps-tracks-color-mode`. +ET-012 не вводит новых ключей и не меняет существующие. Существующие +пользователи увидят треки на z5-z7 при следующей загрузке без потери +выбранных фильтров. + +### REQ-F-19 — Деплой и валидация + +После merge в `main` и деплоя в test-среду: + +1. Открыть `https://openclaw.mva154.duckdns.org/enduro/`, + включить «Публичные треки», установить `zoom = 5` + (через DevTools `window._map.setZoom(5)`), убедиться, что + линии видны. +2. Снять профайл DevTools Network: размер запроса + `/api/gps-tracks/tiles/5/19/9.mvt` ≤ 200 KB. +3. Проверить три тайла z=5 над разными регионами (Москва, Урал, + Сибирь) — все ≤ 200 KB и тело > 0 для регионов с треками. +4. Зафиксировать результаты в `14-deploy-log.md`. + +### REQ-F-20 — Документация + +В `docs/work-items/ET-012/` после Анализа существуют: +- `00-business-request.md` (есть) +- `01-brd.md` +- `02-trz.md` (этот файл) +- `03-acceptance-criteria.md` +- `04-test-plan.yaml` +- `04b-ui-test-cases.md` + +После реализации добавляются: `10-tech-risks.md` (опционально), +`12-review.md`, `13-test-report.md`, `14-deploy-log.md`. + +## 4. Не-функциональные требования + +### NFR-01 — Производительность сервера + +- p95 `build_gps_mvt` на z=5 при БД 500 треков ≤ 500 мс на CI-runner + (метрика M-6). +- p95 endpoint `/api/gps-tracks/tiles/{5..7}/x/y.mvt` cold ≤ 700 мс, + hit ≤ 50 мс (M-7). +- Не более 10 SQLite-запросов на тайл (в идеале — 2: COUNT + SELECT). + +### NFR-02 — Производительность клиента + +- На z5 рендер слоя не дольше +30 мс по сравнению с состоянием + слой-выключен (замер через MapLibre `map.on('render')` интервал). +- Не вызывает frame-drop ниже 30 FPS на средне-мобильном устройстве + (iPhone 12 / Pixel 5 эквивалент). + +### NFR-03 — Сетевой трафик + +- Размер одного MVT-тайла z=5 ≤ 200 KB до gzip (метрика M-8). +- gzip-compression на nginx даёт обычно ×3-4 по тайлам — финальный + трафик 50-70 KB на тайл. + +### NFR-04 — Кэширование + +- LRU размер `_GPS_TILE_CACHE_MAX = 1024` — не меняем. + Опциональное увеличение до 2048 — на усмотрение разработчика, + если в `PERF-Z5-01` обнаружится частая инвалидация. + +### NFR-05 — Безопасность + +Никаких изменений в auth / CSP / валидации входных данных +ET-012 не вносит. + +### NFR-06 — Совместимость + +- API контракт `/api/gps-tracks*` не меняется → старые клиенты + работают без обновления. +- Существующие browser-tabs с открытой картой при следующей загрузке + получат новые лимиты автоматически (никакой миграции + localStorage не нужно). + +### NFR-07 — Логирование + +Никаких новых лог-сообщений. Существующее логирование +endpoint `gps_tile` (через `uvicorn.access`) показывает зум, x, y, размер ответа — это достаточно. + +## 5. План работ (для разработчика) + +1. **Backend: расширить `build_gps_mvt` tier-таблицу** (REQ-F-03). +2. **Backend: расширить `_simplify_coords` tier-таблицу** (REQ-F-04). +3. **Unit-тесты zoom-tier и simplify** (REQ-F-09, F-10). +4. **Integration-тесты endpoint z5-z7** (REQ-F-11, F-12). +5. **Performance-тест PERF-Z5-01** (REQ-F-13). Если не проходит — + ужесточить `limit` в REQ-F-03. +6. **Frontend: понизить `GPS_TRACKS_MIN_ZOOM` до 5** (REQ-F-01). +7. **Frontend: line-width stops для z5** в основном слое и halo + (REQ-F-05, F-06). +8. **Frontend: текст hint** (REQ-F-07). +9. **Прогон `make lint`, `make test`.** +10. **Code review → merge → deploy в test.** +11. **Ручная проверка REQ-F-19.** +12. **Прогон UI-тестов** по `04b-ui-test-cases.md`. +13. **Запись результатов** в `13-test-report.md` и `14-deploy-log.md`. + +## 6. Открытые вопросы и решения по умолчанию + +| Вопрос | Решение по умолчанию | +| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Опускать ли порог ещё ниже (z3-z4)? | **Нет.** На z3-z4 даже 10-км треки превращаются в точку — нужна heat-map. Это отдельный work item. | +| Увеличить ли `_GPS_TILE_CACHE_MAX`? | **Нет в MVP.** Текущие 1024 покрывают z5..z11. Только если PERF-Z5-01 покажет деградацию. | +| Уменьшать ли buffer endpoint'а до 5 % для z≤6? | **Нет в MVP.** 10 % буфер на z5-тайле в большинстве регионов не критичен (≈ 100 км запас в bbox-запросе вместо 1250). Можно вернуться, если PERF-Z5-01 не пройдёт. | +| Делать ли разные tier для color-by-source vs color-by-activity на z5? | **Нет.** Геометрия одна, цвет — runtime-выражение MapLibre, не зависит от tier. | +| Что показывать пользователю на z3-z4? | Hint «Зум 5+» (REQ-F-07) даёт явное объяснение. Heat-map — отдельный work item. | +| Сохранять ли поведение «слой пуст, но включён» через localStorage на z<5? | **Да** — чекбокс остаётся checked, hint объясняет, что нужно зумить. Логика уже есть в `_syncGpsLayersVisibility`. | +| Сразу прогружать MVT z5 при включении слоя, если карта на z2? | **Нет.** Source.minzoom=5 защищает: тайлы не запрашиваются до z≥5. Не меняем. | +| Менять ли LRU FIFO на настоящий LRU? | **Нет в MVP.** При работе с 10-20 тайлами в кадре FIFO эквивалентен LRU; разница только при больших кэшах. | diff --git a/docs/work-items/ET-012/03-acceptance-criteria.md b/docs/work-items/ET-012/03-acceptance-criteria.md new file mode 100644 index 0000000..ea88898 --- /dev/null +++ b/docs/work-items/ET-012/03-acceptance-criteria.md @@ -0,0 +1,214 @@ +--- +type: acceptance-criteria +work_item_id: ET-012 +title: "Acceptance Criteria: Показывать пользовательские треки с зума z5" +version: 1 +status: draft +created_at: 2026-06-04 +updated_at: 2026-06-04 +authors: + - "agent:analyst" +--- + +# Acceptance Criteria — ET-012 + +Критерии в Gherkin-стиле. Все — обязательные. Задача считается +принятой, когда каждый критерий прошёл проверку (автоматическую +в CI или ручную в test-среде). + +## AC-01 — Константа `GPS_TRACKS_MIN_ZOOM` понижена до 5 + +**Given** ветка `feature/ET-012-z5-z8` с правками +**When** проверяется код +**Then**: +- В `src/web/gps_tracks.js` есть ровно одно объявление + `const GPS_TRACKS_MIN_ZOOM = 5;` (с возможным trailing comment). +- `grep -R "GPS_TRACKS_MIN_ZOOM" src/web/` не находит других значений, + кроме `5`. + +## AC-02 — Vector-source `gps-tracks-tiles` имеет minzoom=5 + +**Given** test-среда после деплоя ET-012 +**When** в DevTools выполнить +```js +window._map.getSource('gps-tracks-tiles').minzoom +``` +**Then** результат `5`. + +## AC-03 — При z=5 слой публичных треков виден + +**Given** пользователь на `https://openclaw.mva154.duckdns.org/enduro/`, +включён чекбокс «Публичные треки», БД содержит ≥ 50 треков по ЦФО +длиннее 10 км +**When** установить `zoom = 5` (через DevTools или панорамированием) +и центр карты над ЦФО +**Then**: +- На карте видны линии треков (визуально — не менее 3 различимых + линий в кадре). +- `window._map.getLayoutProperty('gps-tracks-layer-mvt', 'visibility') === 'visible'`. +- Hint `#public-tracks-zoom-hint` имеет `display: none`. + +## AC-04 — При z=6 и z=7 слой публичных треков виден + +Аналогично AC-03 для z=6 (lim min_length = 5 км) и z=7 +(min_length = 2 км). Количество видимых линий в кадре ≥ AC-03. + +## AC-05 — Hint «Зум 5+» появляется при z<5 + +**Given** включён чекбокс «Публичные треки» +**When** установить `zoom = 4` +**Then**: +- Hint `#public-tracks-zoom-hint` имеет `display: inline` (или иное + ненулевое отображение). +- Текст hint'а — «Зум 5+». +- На карте нет линий публичных треков (vector-source не запрашивает + тайлы при `zoom < source.minzoom`). + +## AC-06 — Регрессия z8-z11: слой работает как прежде + +**Given** ветка после ET-012 +**When** установить `zoom = 8, 9, 10, 11` поочерёдно +**Then**: +- На каждом зуме слой `gps-tracks-layer-mvt` имеет `visibility: visible`. +- Набор отображаемых треков не уже, чем до ET-012 (за вычетом того, + что в z=8 включаются ВСЕ треки независимо от длины, как было). +- Запросы `/api/gps-tracks/tiles/{z}/x/y.mvt` возвращают 200. + +## AC-07 — Регрессия z12+: GeoJSON-слой работает как прежде + +**Given** включён чекбокс +**When** установить `zoom = 12, 13, 14, 15` +**Then**: +- `gps-tracks-layer-mvt` имеет `visibility: none`. +- `gps-tracks-layer-geo` имеет `visibility: visible`. +- На карте видны те же треки, что и до ET-012. + +## AC-08 — Читаемость карты на z5 (качественный критерий) + +**Given** test-среда с ≥ 200 треками по ЦФО (после E2E-PROD-01/02 из ET-009) +**When** скриншот при `zoom = 5`, центр над Москвой +**Then**: +- На скриншоте `et012-z5-readable.png` видны минимум 3 разных + «нити» в разных квадрантах кадра. +- Нет «сплошной заливки» одной зоны (треки не сливаются в кашу). +- Допустимо отличать «нить» как любую видимую линию длиной ≥ 20 px + в кадре. + +Проверка ручная по скриншоту в `13-test-report.md`. + +## AC-09 — Производительность endpoint z=5 в test-среде + +**Given** test-среда +**When** 10 раз подряд `curl -w '%{time_total}\n' -o /dev/null +"https://openclaw.mva154.duckdns.org/enduro/api/gps-tracks/tiles/5/19/9.mvt"`, +последовательно (первый — cold, последующие — cache hits) +**Then**: +- Cold-запрос ≤ 1.5 с (M-7 c запасом для сети). +- Median последующих ≤ 200 мс (cache hit). +- HTTP 200 на каждый запрос. +- Размер тела ≤ 200 KB (после gzip-decompression). + +## AC-10 — Размер MVT-тайла z=5 не превышает 200 KB + +**Given** test-среда +**When** скачать тайл `tiles/5/19/9.mvt` (Москва) и `tiles/5/20/9.mvt` +(восток ЦФО) +**Then** размер тела ≤ 200 KB для каждого. + +## AC-11 — Unit-тесты zoom-tier зелёные + +**Given** ветка +**When** `pytest tests/unit/test_gps_mvt_zoom_tiers.py -v` +**Then** все UT-Z5-*, UT-Z6-*, UT-Z7-*, UT-Z8-*, UT-Z12-* проходят. + +## AC-12 — Unit-тесты simplify зелёные + +**Given** ветка +**When** `pytest tests/unit/test_gps_mvt_simplify.py -v` +**Then** все UT-SIMP-Z5-*, UT-SIMP-Z6-*, UT-SIMP-Z7-*, UT-SIMP-Z10-*, +UT-SIMP-Z12-* проходят. + +## AC-13 — Integration-тесты endpoint z5-z7 зелёные + +**Given** ветка +**When** `pytest tests/integration/test_gps_tile_z5_z7.py -v` +**Then** все IT-Z5-*, IT-Z6-*, IT-Z7-*, IT-CACHE-* проходят. + +## AC-14 — Регрессионные тесты ET-008/ET-009 зелёные + +**Given** ветка +**When** `pytest tests/unit/ tests/integration/ -v` (исключая perf-маркер) +**Then** все существующие тесты ET-008 (U-01..U-62 / I-01..I-57) +и ET-009 (UT-ER-*, UT-WL-*, UT-CFG-*, IT-*) проходят без регрессий. + +## AC-15 — Регрессия фильтров на z6 + +**Given** включён слой, на карте `zoom = 6`, видны треки трёх +источников (osm/enduro_russia/wikiloc) +**When** пользователь открывает `#sheet-gps-filters` и снимает галку +«EnduroRussia» +**Then** через ≤ 1.5 с (с учётом инвалидации MVT тайлов через +`map.setFilter`) с карты исчезают линии цвета EnduroRussia, +остальные остаются. + +## AC-16 — Регрессия popup на z6 + +**Given** включён слой, на карте `zoom = 6` или `7`, в кадре есть +длинный (≥ 10 км) трек +**When** пользователь кликает по линии трека +**Then**: +- Открывается popup `.track-popup` с названием, активностью, длиной, + источниками. +- Если трек из источника `osm` — в popup'е есть кнопка «Скачать GPX» + (`.track-popup-download-btn`). +- Клик по кнопке скачивает GPX-файл (контракт ET-011 не нарушен). + +## AC-17 — Halo на спутнике на z5 виден, но не «глушит» подложку + +**Given** включён слой, переключена базовая подложка на спутник +(`#base-btn-satellite`), `zoom = 5` +**When** скриншот +**Then**: +- Линии видны на тёмной спутниковой подложке (благодаря halo). +- Halo-width ≤ 2 px (т.е. ореол не превращается в «пузырь»). +- `gps-tracks-halo-mvt-satellite.visibility === 'visible'`. + +## AC-18 — Поведение на мобильном (375×667 viewport) + +**Given** Playwright mobile viewport, включён слой, `zoom = 5` +**When** скриншот +**Then**: +- Линии видны. +- Толщина линии по «зрительному ощущению» ≥ 1 пикселя. +- Hint скрыт. + +## AC-19 — Performance-test PERF-Z5-01 + +**Given** ветка +**When** `pytest -m perf tests/performance/test_gps_mvt_z5_perf.py -v` +**Then**: +- PERF-Z5-01 проходит: avg ≤ 200 мс, p95 ≤ 500 мс на CI-runner + при БД 500 треков. + +(Этот тест запускается отдельным джобом / pre-merge gate.) + +## AC-20 — Документация work item полная + +**Given** репо после слияния ET-012 +**When** проверка `docs/work-items/ET-012/` +**Then** существуют: +- `00-business-request.md` +- `01-brd.md` +- `02-trz.md` +- `03-acceptance-criteria.md` +- `04-test-plan.yaml` +- `04b-ui-test-cases.md` +- `12-review.md` (после Review) +- `13-test-report.md` (после Тестирования) +- `14-deploy-log.md` (после Деплоя) + +## AC-21 — `make lint` и `make test` зелёные + +**Given** ветка +**When** `make lint` и `make test` +**Then** обе команды exit-code 0. diff --git a/docs/work-items/ET-012/04-test-plan.yaml b/docs/work-items/ET-012/04-test-plan.yaml new file mode 100644 index 0000000..c89dcc6 --- /dev/null +++ b/docs/work-items/ET-012/04-test-plan.yaml @@ -0,0 +1,401 @@ +--- +type: test-plan +work_item_id: ET-012 +title: "Test Plan: Показывать пользовательские треки с зума z5" +version: 1 +status: draft +created_at: 2026-06-04 +updated_at: 2026-06-04 +authors: + - "agent:analyst" +related: + - "ET-008" + - "ET-009" + - "ET-011" + +scope_note: > + ET-012 опускает порог видимости слоя публичных GPS-треков с z8 до z5. + Изменения локализованы: + - backend mvt.py: zoom-tier для z5/z6 (min_length, limit, tolerance); + - frontend gps_tracks.js: константа GPS_TRACKS_MIN_ZOOM=5, + line-width stops для z5 в основном слое и halo; + - index.html: текст hint «Зум 5+». + Тест-план фокусируется на: + (1) корректности новых zoom-tier'ов в build_gps_mvt и _simplify_coords; + (2) что endpoint отдаёт нормально-размерные MVT на z5-z7; + (3) что клиент действительно показывает слой на z5-z7; + (4) что регрессий ET-008/009/011 нет; + (5) что производительность не уплыла. + +test_suites: + + - name: unit-mvt-zoom-tiers + type: unit + description: "Тиры min_length_m и limit в build_gps_mvt по зумам" + cases: + - id: UT-Z5-01 + name: "z=5: треки < 10 км отфильтровываются" + input: | + Mock rows: 10 треков, длина = [500, 2000, 3000, 8000, 12000, 15000, 25000, 50000, 80000, 120000]. + Вызов build_gps_mvt(rows, z=5, x=19, y=9). + expected: | + В MVT попадают только треки длиной >= 10000 м, т.е. ровно 6 features. + + - id: UT-Z5-02 + name: "z=5: limit=1500" + input: | + Mock rows: 2000 треков длиной 15 км каждый (все пройдут min_length). + expected: | + В MVT попадают первые 1500 features, остальные отбрасываются. + + - id: UT-Z6-01 + name: "z=6: треки < 5 км отфильтровываются" + input: | + Mock rows: 5 треков, длина = [1000, 3000, 5000, 7000, 10000]. + expected: | + В MVT 3 features (5000, 7000, 10000). + + - id: UT-Z6-02 + name: "z=6: limit=2000" + input: | + Mock rows: 2500 треков длиной 6 км каждый. + expected: | + В MVT 2000 features. + + - id: UT-Z7-01 + name: "z=7: регрессия — поведение до ET-012" + input: | + Mock rows: 4 трека [1000, 2000, 3000, 5000]. + expected: | + В MVT 3 features (2000, 3000, 5000), как раньше. + + - id: UT-Z8-01 + name: "z=8: регрессия — нет min_length-фильтра" + input: | + Mock rows: 4 трека [500, 1000, 2000, 5000]. + expected: | + В MVT 4 features, limit=8000. + + - id: UT-Z12-01 + name: "z=12: регрессия — limit=25000, без min_length" + input: | + Mock rows: 100 треков любой длины. + expected: | + В MVT 100 features. + + - name: unit-mvt-simplify + type: unit + description: "Tolerance Douglas-Peucker по зумам в _simplify_coords" + cases: + - id: UT-SIMP-Z5-01 + name: "z=5: прямая линия 100 точек → ≤ 5 точек" + input: | + coords = [(37.0 + i*0.001, 55.0 + i*0.001) for i in range(100)] + (приблизительно прямая ~10 км по диагонали) + expected: | + len(_simplify_coords(coords, 5)) <= 5 + + - id: UT-SIMP-Z5-02 + name: "z=5: зигзаг с амплитудой < tolerance → 2 точки" + input: | + coords = зигзаг 100 точек, амплитуда 0.01° (~1 км) + expected: | + len(_simplify_coords(coords, 5)) == 2 (только концы) + + - id: UT-SIMP-Z6-01 + name: "z=6: зигзаг 5 км → видны крупные пики" + input: | + coords = зигзаг 100 точек, амплитуда 0.05° (~5 км) + expected: | + len(_simplify_coords(coords, 6)) > 5 + + - id: UT-SIMP-Z7-01 + name: "z=7: регрессия — tolerance = 0.008" + input: | + coords = зигзаг 100 точек, амплитуда 0.005° (~500 м) + expected: | + len(_simplify_coords(coords, 7)) близок к до-ET-012 значению + (округлённо в пределах +/-1). + + - id: UT-SIMP-Z10-01 + name: "z=10: регрессия — tolerance = 0.0005" + input: | + coords = зигзаг 100 точек, амплитуда 0.001° (~100 м) + expected: | + Поведение совпадает с до-ET-012 (контрольный snapshot). + + - id: UT-SIMP-Z12-01 + name: "z=12: регрессия — без упрощения" + input: | + coords = 100 точек + expected: | + _simplify_coords(coords, 12) is coords (или эквивалент) + + - id: UT-SIMP-EDGE-01 + name: "Слишком мало точек → возвращаем как есть" + input: | + coords = [(37.0, 55.0), (37.001, 55.001)] (2 точки) + expected: | + На любом zoom — функция возвращает [(37.0, 55.0), (37.001, 55.001)]. + + - id: UT-SIMP-EDGE-02 + name: "DP схлопнул до < 2 точек → возвращаем оригинал" + input: | + coords = 100 одинаковых точек (вырожденный трек) + expected: | + Функция возвращает оригинальный coords, не пустой список. + + - name: integration-tile-endpoint + type: integration + description: "Endpoint /api/gps-tracks/tiles/{z}/{x}/{y}.mvt на z=5..7" + cases: + - id: IT-Z5-01 + name: "Тайл z=5 над Москвой: 200, тело > 0, < 200 KB" + input: | + Test DB: 50 треков по ЦФО, длина 12..30 км каждый. + GET /api/gps-tracks/tiles/5/19/9.mvt + expected: | + status 200, + Content-Type 'application/x-protobuf', + 0 < len(body) < 200_000 + + - id: IT-Z5-02 + name: "Тайл z=5 с большой БД: limit держит размер" + input: | + Test DB: 200 треков по ЦФО, длина 12..30 км. + GET /api/gps-tracks/tiles/5/19/9.mvt + expected: | + status 200, + len(body) < 200_000, + mapbox_vector_tile.decode(body)['gps_tracks']['features'] <= 1500 + + - id: IT-Z5-03 + name: "Тайл z=5 над пустым регионом: пустое тело" + input: | + Test DB: те же 50 треков по ЦФО. + GET /api/gps-tracks/tiles/5/4/12.mvt (Тихий океан) + expected: | + status 200, + len(body) == 0 + + - id: IT-Z6-01 + name: "Тайл z=6 над Москвой: больше фич, чем z=5" + input: | + Test DB: 100 треков, длина 4..20 км. + GET /api/gps-tracks/tiles/6/38/19.mvt + expected: | + status 200, + features_count(z=6) >= features_count(z=5) для того же региона, + len(body) < 200_000 + + - id: IT-Z7-01 + name: "Тайл z=7 над Москвой: регрессия + плюс короткие треки" + input: | + GET /api/gps-tracks/tiles/7/77/39.mvt с теми же 100 треками. + expected: | + status 200, + features_count(z=7) >= features_count(z=6), + features_count(z=7) <= 3000 + + - id: IT-CACHE-01 + name: "LRU-кэш: второй запрос — X-Cache: HIT" + input: | + GET /api/gps-tracks/tiles/5/19/9.mvt дважды подряд. + expected: | + 1-й ответ: header X-Cache: MISS. + 2-й ответ: header X-Cache: HIT, тело идентично 1-му. + + - id: IT-CACHE-02 + name: "Сброс кэша через /cache/clear" + input: | + GET tiles/5/19/9.mvt → POST /api/gps-tracks/cache/clear → GET tiles/5/19/9.mvt + expected: | + 1-й ответ MISS, 2-й (после clear) MISS. + + - id: IT-REGRESS-Z8-01 + name: "Регрессия z=8: контракт MVT не изменился" + input: | + GET /api/gps-tracks/tiles/8/154/79.mvt на тестовой БД. + (Тайл-координаты выбраны над Москвой.) + expected: | + features_count(z=8) точно совпадает с snapshot до ET-012 + (записывается в tests/fixtures/gps-tracks/mvt-z8-snapshot.json). + + - id: IT-REGRESS-Z10-01 + name: "Регрессия z=10" + input: | + GET /api/gps-tracks/tiles/10/617/319.mvt + expected: | + features_count(z=10) совпадает с snapshot до ET-012. + + - id: IT-VALID-01 + name: "z вне диапазона — 400" + input: | + GET /api/gps-tracks/tiles/-1/0/0.mvt и tiles/23/0/0.mvt + expected: | + status 400, detail 'Invalid z' + + - name: integration-api-geojson-cutoff + type: integration + description: "GeoJSON-слой не изменился" + cases: + - id: IT-GEO-01 + name: "GET /api/gps-tracks?bbox=… работает как раньше" + input: | + GET /api/gps-tracks?bbox=37,55,38,56&limit=500 + expected: | + status 200, + FeatureCollection с features, total_in_bbox, returned, truncated — + контракт идентичен ET-009. + + - name: performance + type: performance + description: "Производительность build_gps_mvt на z=5" + marker: "@pytest.mark.perf" + cases: + - id: PERF-Z5-01 + name: "build_gps_mvt на z=5 при 500 треках" + input: | + Test DB: 500 треков длиной 12-25 км по ЦФО. + 10 повторных вызовов build_gps_mvt(rows, 5, 19, 9). + expected: | + avg time <= 200 ms, + p95 time <= 500 ms на CI-runner (метрика M-6). + + - id: PERF-Z5-02 + name: "build_gps_mvt на z=5 при 5000 треках (стресс)" + input: | + Test DB: 5000 треков, разные длины. + 5 повторных вызовов. + expected: | + p95 time <= 1500 ms. + + - id: PERF-ENDPOINT-01 + name: "Endpoint p95 на z=5 (cold)" + input: | + 10 cold-запросов tile-endpoint (после cache clear) на test-БД. + expected: | + p95 <= 700 ms. + + - id: PERF-ENDPOINT-02 + name: "Endpoint p95 на z=5 (hot, кэш)" + input: | + 100 повторных запросов одного тайла после прогрева. + expected: | + p95 <= 50 ms. + + - name: regression-existing + type: regression + description: "Регрессия ET-008 / ET-009 / ET-011" + cases: + - id: RG-08-01 + name: "Все unit-тесты ET-008 проходят" + input: "pytest tests/unit/test_gps_*.py -v (за исключением новых ET-012)" + expected: "exit-code 0" + + - id: RG-09-01 + name: "Все unit-тесты ET-009 (parser EnduroRussia/Wikiloc)" + input: "pytest tests/unit/test_gps_tracks_enduro_russia.py tests/unit/test_gps_tracks_wikiloc.py -v" + expected: "exit-code 0" + + - id: RG-11-01 + name: "Тесты ET-011 download GPX" + input: "pytest tests/integration/test_gps_download.py -v" + expected: "exit-code 0" + + - id: RG-INT-01 + name: "Все integration-тесты" + input: "pytest tests/integration/ -v" + expected: "exit-code 0" + + - name: ui-playwright + type: ui + description: "Playwright UI-тесты на test-среде" + reference: "04b-ui-test-cases.md" + cases: + - id: UI-LINK-01 + name: "См. 04b-ui-test-cases.md — TC-UI-01-Z5..TC-UI-12-Z5-Q" + expected: "Каждый TC выполняется и check-visual подтверждается оператором." + + - name: manual-deploy-validation + type: e2e + description: "Ручная проверка в test-среде после деплоя" + marker: "manual" + cases: + - id: E2E-DEPLOY-01 + name: "Включить слой и поставить zoom=5" + steps: + - "Открыть https://openclaw.mva154.duckdns.org/enduro/" + - "Open DevTools, в Console: localStorage.clear() для чистого старта" + - "Click #terrain-toggle" + - "Click #public-tracks-cb (включить)" + - "В Console: window._map.setZoom(5); window._map.setCenter([37.6, 55.7])" + - "Ждать 3 секунды" + - "Visual: видны линии публичных треков" + - "Зафиксировать скриншот в 14-deploy-log.md" + + - id: E2E-DEPLOY-02 + name: "Network: размер тайла z=5" + steps: + - "В DevTools Network отфильтровать по 'tiles/5'" + - "Проверить: каждый ответ ≤ 200 KB (Size column)" + - "Зафиксировать в 14-deploy-log.md" + + - id: E2E-DEPLOY-03 + name: "Уменьшить зум до z=4 — hint показывается" + steps: + - "window._map.setZoom(4)" + - "Visual: hint 'Зум 5+' появился" + - "На карте нет линий публичных треков" + + - id: E2E-DEPLOY-04 + name: "Зум z=12 — переход на GeoJSON" + steps: + - "window._map.setZoom(12)" + - "Wait 1.5s" + - "В DevTools Network отфильтровать по '/api/gps-tracks?bbox'" + - "Запрос ушёл, status 200" + - "На карте видны линии, но из GeoJSON-source (gps-tracks-layer-geo)" + + - id: E2E-DEPLOY-05 + name: "Регрессия: popup и скачивание GPX" + steps: + - "window._map.setZoom(8)" + - "Кликнуть по треку из источника osm" + - "Popup открылся, в нём есть кнопка 'Скачать GPX'" + - "Клик по кнопке скачивает .gpx файл (ET-011 контракт)" + +test_data: + fixtures_dir: "tests/fixtures/gps-tracks/" + fixtures: + - name: "mvt-z8-snapshot.json" + description: "Snapshot число features в тайле z=8/154/79 над Москвой до ET-012 (для IT-REGRESS-Z8-01)" + - name: "mvt-z10-snapshot.json" + description: "Аналогично для z=10/617/319 (IT-REGRESS-Z10-01)" + notes: + - "Snapshot'ы создаются разово до начала разработки ET-012 на текущем состоянии test-БД и кладутся в репо." + - "Для unit-тестов использовать sqlite3.Row mock — реальная БД не нужна." + +test_environment: + unit: + - "pytest tmp_path для временной sqlite (по необходимости)" + - "Mock sqlite3.Row через unittest.mock или фабрика" + integration: + - "Test sqlite БД с фикстурными треками из existing ET-008/009 фабрик" + - "FastAPI TestClient для endpoint вызовов" + performance: + - "Маркер @pytest.mark.perf, не в обычном CI" + - "Запуск перед merge: pytest -m perf" + e2e: + - "Test-среда https://openclaw.mva154.duckdns.org/enduro/" + - "Реальная БД после ET-009 прогона" + - "UI-тесты — см. 04b-ui-test-cases.md (Playwright)" + +ci_gates: + - "Unit-тесты UT-Z*-* и UT-SIMP-* — обязательны (AC-11, AC-12)" + - "Integration IT-Z*-*, IT-CACHE-*, IT-REGRESS-* — обязательны (AC-13)" + - "Регрессия RG-* — обязательна (AC-14)" + - "Performance PERF-Z5-01 — обязателен перед merge (AC-19)" + - "UI-тесты — запуск после деплоя, фиксация в 13-test-report.md" + - "E2E-DEPLOY-* — ручные шаги в 14-deploy-log.md" +--- diff --git a/docs/work-items/ET-012/04b-ui-test-cases.md b/docs/work-items/ET-012/04b-ui-test-cases.md new file mode 100644 index 0000000..08439bf --- /dev/null +++ b/docs/work-items/ET-012/04b-ui-test-cases.md @@ -0,0 +1,375 @@ +--- +type: ui-test-cases +work_item_id: ET-012 +title: "UI Test Cases: Публичные треки на z5-z7" +version: 1 +status: draft +created_at: 2026-06-04 +updated_at: 2026-06-04 +authors: + - "agent:analyst" +related: + - "ET-008" + - "ET-009" + - "ET-011" +--- + +# UI Test Cases — ET-012: Публичные треки на зумах z5-z7 + +Базовый URL: `https://openclaw.mva154.duckdns.org/enduro/` + +ET-012 не добавляет новых UI-компонентов — только меняет нижний +порог видимости слоя публичных треков с z8 до z5 и тонкие настройки +толщины линий/халобокса для малых зумов. UI-тесты проверяют, что: + +1. На z5, z6, z7 слой действительно появляется. +2. Hint обновлён или скрыт корректно. +3. Регрессий ET-008/009/011 нет. +4. На спутнике на z5 линии видны и halo не «глушит» подложку. +5. На мобильном viewport всё работает. + +Селекторы (унаследованы из ET-008/009/011): + +- `#terrain-toggle` — кнопка попапа слоёв. +- `#public-tracks-cb` — чекбокс «Публичные треки». +- `#public-tracks-zoom-hint` — hint «Зум 5+». +- `#public-tracks-filters-btn` — кнопка «Фильтры…» (видна при включённом слое). +- `#sheet-gps-filters` — bottom sheet фильтров. +- `#gps-source-grid input[value='osm' | 'enduro_russia' | 'wikiloc']` — чекбоксы. +- `#base-btn-satellite` — кнопка спутника. +- `.track-popup` / `.track-popup-download-btn` — popup и кнопка скачивания. +- `#map` — карта. + +Предусловие для всех тестов: в БД test-среды есть треки всех трёх +источников (после E2E-PROD-01/02 из ET-009). Все TC выполняются +Playwright'ом против test-среды; check-visual подтверждается +оператором или визуальным diff-тулом. + +Особенность ET-012 — каждый сценарий выставляет zoom программно, +чтобы не зависеть от перетаскивания карты. Команда: +```js +window._map.setZoom(N); +window._map.setCenter([37.6, 55.7]); // Москва, по умолчанию +``` +выполняется через `page.evaluate(...)`. + +--- + +### TC-UI-01-Z5 — На z=5 слой публичных треков виден + +- тип: ui +- viewport: desktop + +шаги: +1. navigate: https://openclaw.mva154.duckdns.org/enduro/ +2. wait: 5000 +3. click: "#terrain-toggle" +4. wait: 500 +5. click: "#public-tracks-cb" +6. wait: 3000 +7. evaluate: window._map.setZoom(5); window._map.setCenter([37.6, 55.7]); +8. wait: 4000 +9. screenshot: "et012-01-z5-tracks-visible" +10. check-visual: "На карте при zoom=5 (виден кусок Восточной Европы / ЦФО) поверх подложки нарисованы линии публичных треков как минимум двух разных цветов (по источнику). Линии тонкие, но различимые на дисплее. Hint #public-tracks-zoom-hint скрыт. Чекбокс #public-tracks-cb включён." + +--- + +### TC-UI-02-Z6 — На z=6 слой виден, треков больше чем на z5 + +- тип: ui +- viewport: desktop + +шаги: +1. navigate: https://openclaw.mva154.duckdns.org/enduro/ +2. wait: 5000 +3. click: "#terrain-toggle" +4. wait: 500 +5. click: "#public-tracks-cb" +6. wait: 3000 +7. evaluate: window._map.setZoom(6); window._map.setCenter([37.6, 55.7]); +8. wait: 4000 +9. screenshot: "et012-02-z6-tracks-visible" +10. check-visual: "При zoom=6 (виден кусок Центральной России) на карте видно явно больше линий, чем на z5: появляются треки длиной 5-10 км, которые не прошли фильтр z5. Линии лучше различимы (толще). Hint скрыт." + +--- + +### TC-UI-03-Z7 — На z=7 слой виден, регрессия + +- тип: ui +- viewport: desktop + +шаги: +1. navigate: https://openclaw.mva154.duckdns.org/enduro/ +2. wait: 5000 +3. click: "#terrain-toggle" +4. wait: 500 +5. click: "#public-tracks-cb" +6. wait: 3000 +7. evaluate: window._map.setZoom(7); window._map.setCenter([37.6, 55.7]); +8. wait: 4000 +9. screenshot: "et012-03-z7-tracks-visible" +10. check-visual: "При zoom=7 видны треки длиной от 2 км и выше (как было до ET-012). На карте — заметная сеть. Поведение должно соответствовать прежнему 'z=8 минус один уровень', но с min_length=2000 (т.е. чуть строже фильтр чем z8). Hint скрыт." + +--- + +### TC-UI-04-HINT-OFF — Hint «Зум 5+» скрыт при z=5 + +- тип: ui +- viewport: desktop + +шаги: +1. navigate: https://openclaw.mva154.duckdns.org/enduro/ +2. wait: 5000 +3. click: "#terrain-toggle" +4. wait: 500 +5. click: "#public-tracks-cb" +6. wait: 2000 +7. evaluate: window._map.setZoom(5); +8. wait: 1500 +9. screenshot: "et012-04-hint-off-z5" +10. check-visual: "Элемент #public-tracks-zoom-hint имеет display:none (не виден в попапе слоёв). Чекбокс «Публичные треки» включён. Никакой подсказки 'нужно увеличить зум' не показано." + +--- + +### TC-UI-05-HINT-ON — Hint «Зум 5+» виден при z=4 + +- тип: ui +- viewport: desktop + +шаги: +1. navigate: https://openclaw.mva154.duckdns.org/enduro/ +2. wait: 5000 +3. click: "#terrain-toggle" +4. wait: 500 +5. click: "#public-tracks-cb" +6. wait: 2000 +7. evaluate: window._map.setZoom(4); +8. wait: 1500 +9. screenshot: "et012-05-hint-on-z4" +10. check-visual: "В попапе слоёв (#terrain-popup) рядом с чекбоксом «Публичные треки» виден hint с текстом «Зум 5+». На карте линий публичных треков нет — vector-source не запрашивает тайлы при zoom < minzoom=5." + +--- + +### TC-UI-06-FILTER-Z6 — Фильтр источников работает на z6 + +- тип: ui +- viewport: desktop + +шаги: +1. navigate: https://openclaw.mva154.duckdns.org/enduro/ +2. wait: 5000 +3. click: "#terrain-toggle" +4. wait: 500 +5. click: "#public-tracks-cb" +6. wait: 3000 +7. evaluate: window._map.setZoom(6); window._map.setCenter([37.6, 55.7]); +8. wait: 4000 +9. screenshot: "et012-06a-z6-all-sources" +10. check-visual: "На z=6 видны треки разных цветов (нескольких источников)." +11. click: "#public-tracks-filters-btn" +12. wait: 800 +13. click: "#gps-source-grid input[value='enduro_russia']" +14. wait: 1500 +15. screenshot: "et012-06b-z6-no-enduro-russia" +16. check-visual: "Чекбокс EnduroRussia снят. На z=6 линии цвета EnduroRussia (характерный красноватый по дефолтной палитре) исчезли. Линии osm/wikiloc остались. Регрессия фильтра — поведение идентично z=8." + +--- + +### TC-UI-07-POPUP-Z6 — Popup трека открывается на z6 + +- тип: ui +- viewport: desktop + +шаги: +1. navigate: https://openclaw.mva154.duckdns.org/enduro/ +2. wait: 5000 +3. click: "#terrain-toggle" +4. wait: 500 +5. click: "#public-tracks-cb" +6. wait: 3000 +7. evaluate: window._map.setZoom(6); window._map.setCenter([37.6, 55.7]); +8. wait: 4000 +9. click: "#map" +10. wait: 1500 +11. screenshot: "et012-07-popup-z6" +12. check-visual: "При клике в линию трека (или близко к ней) открылся popup .track-popup с названием, активностью, длиной, списком источников. Если трек из источника osm — внутри есть кнопка .track-popup-download-btn (ET-011 регрессия). Popup корректно позиционирован, не уходит за границы карты." + +--- + +### TC-UI-08-Z11-REGRESS — На z=11 слой по-прежнему виден (регрессия) + +- тип: ui +- viewport: desktop + +шаги: +1. navigate: https://openclaw.mva154.duckdns.org/enduro/ +2. wait: 5000 +3. click: "#terrain-toggle" +4. wait: 500 +5. click: "#public-tracks-cb" +6. wait: 3000 +7. evaluate: window._map.setZoom(11); window._map.setCenter([37.6, 55.7]); +8. wait: 4000 +9. screenshot: "et012-08-z11-regress" +10. check-visual: "На zoom=11 слой публичных треков виден; на карте много линий разных цветов; поведение визуально идентично состоянию ДО ET-012 (тот же набор треков, та же толщина 1.5-1.75 px согласно interpolate-выражению)." + +--- + +### TC-UI-09-Z12-CUTOFF — На z=12 переход на GeoJSON + +- тип: ui +- viewport: desktop + +шаги: +1. navigate: https://openclaw.mva154.duckdns.org/enduro/ +2. wait: 5000 +3. click: "#terrain-toggle" +4. wait: 500 +5. click: "#public-tracks-cb" +6. wait: 3000 +7. evaluate: window._map.setZoom(12); window._map.setCenter([37.6, 55.7]); +8. wait: 5000 +9. screenshot: "et012-09-z12-geojson" +10. check-visual: "На zoom=12 публичные треки видны (через GeoJSON-source). В DevTools Network должен быть запрос /api/gps-tracks?bbox=... (а не tiles/12/...). Регрессия cutoff поведения не нарушена." + +--- + +### TC-UI-10-Z5-MOBILE — На мобильном при z=5 слой виден + +- тип: ui +- viewport: mobile + +шаги: +1. navigate: https://openclaw.mva154.duckdns.org/enduro/ +2. wait: 5000 +3. click: "#terrain-toggle" +4. wait: 500 +5. click: "#public-tracks-cb" +6. wait: 3000 +7. evaluate: window._map.setZoom(5); window._map.setCenter([37.6, 55.7]); +8. wait: 5000 +9. screenshot: "et012-10-z5-mobile" +10. check-visual: "На мобильном viewport (375×667) при zoom=5 видны линии публичных треков. Линии тонкие, но различимые (минимум 1 физический пиксель). Hint скрыт. Bottom sheet с настройками слоёв закрывается корректно после клика по карте." + +--- + +### TC-UI-11-Z5-SAT — На спутнике на z=5 halo читается, не глушит подложку + +- тип: ui +- viewport: desktop + +шаги: +1. navigate: https://openclaw.mva154.duckdns.org/enduro/ +2. wait: 5000 +3. click: "#terrain-toggle" +4. wait: 500 +5. click: "#public-tracks-cb" +6. wait: 3000 +7. click: "#base-btn-satellite" +8. wait: 5000 +9. evaluate: window._map.setZoom(5); window._map.setCenter([37.6, 55.7]); +10. wait: 5000 +11. screenshot: "et012-11-z5-satellite-halo" +12. check-visual: "На спутниковой подложке при zoom=5 видны цветные линии треков с тонким белым halo (контур ~1.8 px). Halo делает линии читаемыми на тёмных участках космоснимка, но не превращается в 'пузырь' и не закрывает деталей подложки. Слой gps-tracks-halo-mvt-satellite имеет visibility:visible." + +--- + +### TC-UI-12-Z5-Q — Качественная проверка читаемости на z5 + +- тип: ui +- viewport: desktop +- условие: запускается после E2E-PROD-01 (БД содержит ≥ 200 треков) + +шаги: +1. navigate: https://openclaw.mva154.duckdns.org/enduro/ +2. wait: 5000 +3. click: "#terrain-toggle" +4. wait: 500 +5. click: "#public-tracks-cb" +6. wait: 4000 +7. evaluate: window._map.setZoom(5); window._map.setCenter([37.6, 55.7]); +8. wait: 5000 +9. screenshot: "et012-12-z5-readability" +10. check-visual: "На скриншоте видны 3+ различимых нити (линии длиной ≥ 20 px) в разных квадрантах кадра. Нет 'сплошной заливки' одной зоны (треки не сливаются в большое цветное пятно). Подложка карты остаётся читаемой. Качественная проверка — оператор смотрит и принимает либо отбраковывает. При отбраковке: ужесточить limit/min_length в build_gps_mvt (REQ-F-03)." + +--- + +### TC-UI-13-Z5-PAN — Панорамирование на z=5 без зависаний + +- тип: ui +- viewport: desktop + +шаги: +1. navigate: https://openclaw.mva154.duckdns.org/enduro/ +2. wait: 5000 +3. click: "#terrain-toggle" +4. wait: 500 +5. click: "#public-tracks-cb" +6. wait: 3000 +7. evaluate: window._map.setZoom(5); window._map.setCenter([37.6, 55.7]); +8. wait: 4000 +9. evaluate: window._map.panBy([300, 0]); +10. wait: 2500 +11. evaluate: window._map.panBy([0, 300]); +12. wait: 2500 +13. evaluate: window._map.panBy([-300, 0]); +14. wait: 2500 +15. screenshot: "et012-13-z5-pan-complete" +16. check-visual: "После трёх pan-шагов на z=5 карта показывает Восток ЦФО (или соседний регион). Тайлы соседних областей подгружены, нет 'белых дыр'. Тайл-LRU отрабатывает: возврат на исходную область (центр Москвы) — мгновенный (cache hit). Перфоманс субъективно гладкий, нет блокировок UI." + +--- + +### TC-UI-14-Z5-COLOR-ACTIVITY — Color-by-activity на z=5 + +- тип: ui +- viewport: desktop + +шаги: +1. navigate: https://openclaw.mva154.duckdns.org/enduro/ +2. wait: 5000 +3. click: "#terrain-toggle" +4. wait: 500 +5. click: "#public-tracks-cb" +6. wait: 3000 +7. evaluate: window._map.setZoom(5); window._map.setCenter([37.6, 55.7]); +8. wait: 4000 +9. click: "#public-tracks-filters-btn" +10. wait: 800 +11. click: "#gps-color-by-activity" +12. wait: 1500 +13. screenshot: "et012-14-z5-color-by-activity" +14. check-visual: "На z=5 активен переключатель «По активности». Линии перекрашены по activity_type (enduro/moto/offroad/bicycle). Видно минимум 2 разных цвета. Регрессия — color-mode тоggle работает идентично z=8+." + +--- + +### TC-UI-15-DARK-Z5 — Тёмная тема на z=5 + +- тип: ui +- viewport: desktop + +шаги: +1. navigate: https://openclaw.mva154.duckdns.org/enduro/ +2. wait: 5000 +3. evaluate: localStorage.setItem('theme', 'dark'); location.reload(); +4. wait: 5000 +5. click: "#terrain-toggle" +6. wait: 500 +7. click: "#public-tracks-cb" +8. wait: 3000 +9. evaluate: window._map.setZoom(5); window._map.setCenter([37.6, 55.7]); +10. wait: 5000 +11. screenshot: "et012-15-z5-dark" +12. check-visual: "При тёмной теме на z=5 линии публичных треков видны и читаются на тёмной подложке. Цвета линий не изменились (палитра задана в коде). Регрессия dark-theme." + +--- + +### Заметки по запуску + +- Все TC можно автоматизировать в Playwright; check-visual — через + `expect(page).toHaveScreenshot(...)` или визуальный baseline. +- Скриншоты складываются в `docs/work-items/ET-012/screenshots/` + и пришиваются к `13-test-report.md`. +- При первой регрессии TC-UI-12-Z5-Q (нечитаемая карта на z5) + возвращаемся к разработчику с просьбой ужесточить + `min_length_m`/`limit` для z5 (REQ-F-03) — это норма + калибровки, не баг ETLкета.