auto-sync: 2026-05-13 00:50:01

This commit is contained in:
Stream
2026-05-13 00:50:01 +03:00
parent 7060c89903
commit 2fb2063a40
20 changed files with 570 additions and 0 deletions

View File

@@ -0,0 +1,296 @@
# DEV TASK: Terrain JS-логика (баг — функции отсутствуют в app.js)
**Статус:** Ready for dev
**Проект:** enduro-trails
**Фаза:** 5.4 (фикс)
---
## Цель
> Добавить JS-функции `toggleTerrainPopup()` и `onTerrainCheckbox()` в app.js. Кнопка рельеф должна открывать попап, чекбоксы — включать/выключать слои гипсометрии и отмывки на карте.
## Архитектура
HTML-разметка (кнопка `#terrain-toggle`, попап `#terrain-popup`, чекбоксы) и CSS уже на месте. Нужно только JS:
- `toggleTerrainPopup()` — показать/скрыть попап, toggle класс `.active` на кнопке
- `onTerrainCheckbox()` — добавить/удалить raster source+layer для hypso и hillshade
- `updateHillshadeAvailability()` — disable чекбокс hillshade на зуме < 10
- Персистентность через localStorage
## Стек
- MapLibre GL JS (уже подключён, доступен как `window._map`)
- Тайлы terrain: `https://openclaw.mva154.duckdns.org/enduro/terrain/hypso/{z}/{x}/{y}.png` и `.../hillshade/{z}/{x}/{y}.png`
- Формат тайлов: TMS (нужен `scheme: 'tms'` в source)
- Bounds: `[35, 45, 55, 62]`
---
## Инфраструктура
| Параметр | Значение |
|----------|----------|
| Сервер | `slin@82.22.50.71` (пароль: `motoZ@yaz2010`) |
| Контейнер | `prototype-enduro-trails-1` |
| Файл | `/home/slin/enduro-trails/prototype/static/app.js` (2625 строк) |
| URL | `https://openclaw.mva154.duckdns.org/enduro/` |
| Деплой | SSH → docker cp (БЕЗ рестарта!) |
---
## Файловая карта
| Действие | Файл | Ответственность |
|----------|------|-----------------|
| Изменить | `static/app.js` (добавить в конец) | Terrain JS-логика |
---
## Задачи
### Task 1: Добавить terrain JS-функции в app.js
**Файлы:**
- Изменить: `/home/slin/enduro-trails/prototype/static/app.js` — добавить в конец файла
**Шаги:**
- [ ] **1.1** Скачать актуальный app.js с сервера в workspace:
```bash
# Через ssh2 — скопировать /home/slin/enduro-trails/prototype/static/app.js
# в /home/node/.openclaw/workspace/tasks/enduro-trails/prototype/static/app.js
```
- [ ] **1.2** Добавить в конец app.js следующий код:
```javascript
// ═══════════════════════════════════════════
// TERRAIN LAYERS (Phase 5.4)
// ═══════════════════════════════════════════
const TERRAIN_BASE_URL = window.location.pathname.replace(/\/[^/]*$/, '') + '/terrain';
function toggleTerrainPopup() {
const popup = document.getElementById('terrain-popup');
const btn = document.getElementById('terrain-toggle');
if (!popup || !btn) return;
const isVisible = popup.style.display !== 'none';
popup.style.display = isVisible ? 'none' : 'block';
btn.classList.toggle('active', !isVisible);
// Close on outside click
if (!isVisible) {
setTimeout(() => {
document.addEventListener('click', closeTerrainOnOutside);
}, 10);
} else {
document.removeEventListener('click', closeTerrainOnOutside);
}
}
function closeTerrainOnOutside(e) {
const popup = document.getElementById('terrain-popup');
const btn = document.getElementById('terrain-toggle');
if (!popup.contains(e.target) && e.target !== btn && !btn.contains(e.target)) {
popup.style.display = 'none';
btn.classList.remove('active');
document.removeEventListener('click', closeTerrainOnOutside);
}
}
function onTerrainCheckbox() {
const map = window._map;
if (!map) return;
const hypsoChecked = document.getElementById('terrain-hypso-cb').checked;
const hillshadeChecked = document.getElementById('terrain-hillshade-cb').checked;
// Save state
localStorage.setItem('terrain-hypso', hypsoChecked ? '1' : '0');
localStorage.setItem('terrain-hillshade', hillshadeChecked ? '1' : '0');
// Update button active state
const btn = document.getElementById('terrain-toggle');
btn.classList.toggle('active', hypsoChecked || hillshadeChecked);
// Apply layers
applyTerrainLayer('terrain-hypso', TERRAIN_BASE_URL + '/hypso/{z}/{x}/{y}.png', hypsoChecked, 0.55, 5, 15);
applyTerrainLayer('terrain-hillshade', TERRAIN_BASE_URL + '/hillshade/{z}/{x}/{y}.png', hillshadeChecked, 0.40, 10, 15);
}
function applyTerrainLayer(id, tileUrl, enabled, opacity, minzoom, maxzoom) {
const map = window._map;
if (!map) return;
const sourceId = id + '-source';
if (enabled) {
// Add source if not exists
if (!map.getSource(sourceId)) {
map.addSource(sourceId, {
type: 'raster',
tiles: [tileUrl],
tileSize: 256,
scheme: 'tms',
bounds: [35, 45, 55, 62],
minzoom: minzoom,
maxzoom: maxzoom
});
}
// Add layer if not exists
if (!map.getLayer(id)) {
// Insert before first road/trail layer for correct z-order
const firstTrailLayer = map.getStyle().layers.find(l =>
l.id.startsWith('trails-') || l.id.startsWith('poi-')
);
map.addLayer({
id: id,
type: 'raster',
source: sourceId,
paint: {
'raster-opacity': opacity
},
minzoom: minzoom,
maxzoom: maxzoom
}, firstTrailLayer ? firstTrailLayer.id : undefined);
}
} else {
// Remove layer and source
if (map.getLayer(id)) map.removeLayer(id);
if (map.getSource(sourceId)) map.removeSource(sourceId);
}
}
function updateHillshadeAvailability() {
const map = window._map;
if (!map) return;
const zoom = map.getZoom();
const cb = document.getElementById('terrain-hillshade-cb');
const hint = document.getElementById('terrain-hillshade-hint');
const label = cb ? cb.closest('.terrain-checkbox') : null;
if (zoom < 10) {
if (cb) cb.disabled = true;
if (label) label.classList.add('disabled');
if (hint) hint.style.display = 'inline';
} else {
if (cb) cb.disabled = false;
if (label) label.classList.remove('disabled');
if (hint) hint.style.display = 'none';
}
}
function restoreTerrainState() {
const hypso = localStorage.getItem('terrain-hypso') === '1';
const hillshade = localStorage.getItem('terrain-hillshade') === '1';
const hypsoCb = document.getElementById('terrain-hypso-cb');
const hillshadeCb = document.getElementById('terrain-hillshade-cb');
if (hypsoCb) hypsoCb.checked = hypso;
if (hillshadeCb) hillshadeCb.checked = hillshade;
if (hypso || hillshade) {
onTerrainCheckbox();
}
// Update button active state
const btn = document.getElementById('terrain-toggle');
if (btn) btn.classList.toggle('active', hypso || hillshade);
}
// Hook into map load and zoom changes
(function initTerrain() {
const map = window._map;
if (map) {
map.on('zoomend', updateHillshadeAvailability);
map.on('style.load', () => {
// Re-apply terrain after style change (theme switch)
setTimeout(restoreTerrainState, 100);
});
// Initial state
updateHillshadeAvailability();
restoreTerrainState();
} else {
// Map not ready yet, wait
const interval = setInterval(() => {
if (window._map) {
clearInterval(interval);
window._map.on('zoomend', updateHillshadeAvailability);
window._map.on('style.load', () => {
setTimeout(restoreTerrainState, 100);
});
updateHillshadeAvailability();
restoreTerrainState();
}
}, 500);
}
})();
```
- [ ] **1.3** Загрузить на сервер и docker cp:
```bash
# SFTP upload app.js → /home/slin/enduro-trails/prototype/static/app.js
# Затем:
docker cp /home/slin/enduro-trails/prototype/static/app.js prototype-enduro-trails-1:/app/static/app.js
```
⚠️ НЕ рестартовать контейнер!
- [ ] **1.4** Проверить что функции на месте:
```bash
docker exec prototype-enduro-trails-1 grep -c "toggleTerrainPopup\|onTerrainCheckbox\|applyTerrainLayer" /app/static/app.js
# Ожидаемый результат: >= 3
```
**Критерий готовности:** Функции `toggleTerrainPopup`, `onTerrainCheckbox`, `applyTerrainLayer`, `updateHillshadeAvailability`, `restoreTerrainState` присутствуют в app.js на сервере.
---
## Проверка (Acceptance)
| # | Проверка | Команда / Действие | Ожидаемый результат |
|---|----------|-------------------|---------------------|
| 1 | Функции в app.js | `grep -c toggleTerrainPopup` | >= 1 |
| 2 | Кнопка открывает попап | Клик по 🏔️ | Попап виден |
| 3 | Чекбокс hypso | Включить «Гипсометрия» | Цветной слой на карте |
| 4 | Чекбокс hillshade | Включить «Отмывка» (зум >= 10) | Тени рельефа |
| 5 | Hillshade disabled | Зум < 10 | Чекбокс неактивен, hint «Зум 10+» |
| 6 | Попап закрывается | Клик вне попапа | Попап скрыт |
| 7 | Персистентность | Перезагрузить страницу | Состояние чекбоксов сохранено |
| 8 | Смена темы | Переключить тему | Terrain слои остаются |
---
## Ограничения и контекст
- ⚠️ docker cp БЕЗ рестарта — рестарт перезапишет статику
- ⚠️ SSH через Node.js ssh2 модуль
- ⚠️ Тайлы в формате TMS — обязателен `scheme: 'tms'` в source
- ⚠️ `bounds: [35, 45, 55, 62]` — без этого MapLibre запрашивает тайлы за пределами региона
- ⚠️ Terrain слои должны быть ПОД дорогами/POI (beforeId = первый trails/poi layer)
- ⚠️ После `map.setStyle()` (смена темы) все кастомные source/layer слетают — `restoreTerrainState()` вызывается на `style.load`
- ⚠️ Hillshade тайлы существуют только для зумов 10-15
- 🚫 НЕ трогать index.html и app.css — они уже корректны
---
## Деплой-чеклист
- [ ] app.js скачан с сервера (актуальная версия)
- [ ] Terrain-код добавлен в конец
- [ ] Загружен обратно на сервер
- [ ] docker cp выполнен
- [ ] `grep toggleTerrainPopup` — найдено
- [ ] Кнопка рельеф работает (попап открывается)
- [ ] Гипсометрия включается (цветной слой виден)
---
*Создано: 2026-05-12 | Автор ТЗ: Стрим | Исполнитель: Dev-агент*

View File

@@ -0,0 +1,126 @@
# UI Test Cases: Terrain (Фаза 5.4)
### TC-T-01 — Кнопка рельеф видна
**Тип:** ui
**Viewport:** both
**URL:** https://openclaw.mva154.duckdns.org/enduro/
**Шаги:**
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 3000
3. screenshot: "terrain-btn-visible"
4. check-visual: "Кнопка рельеф (иконка горы) видна в правой панели кнопок. Не обрезана, достаточного размера для тапа."
**Визуальные критерии:**
- Кнопка с иконкой горы видна
- Не перекрыта другими элементами
- Достаточный размер (>44px на мобильном)
---
### TC-T-02 — Попап рельеф открывается
**Тип:** ui
**Viewport:** both
**URL:** https://openclaw.mva154.duckdns.org/enduro/
**Шаги:**
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 3000
3. click: "#terrain-toggle"
4. wait: 500
5. screenshot: "terrain-popup-open"
6. check-visual: "Попап рельеф открылся. Видны два чекбокса: Гипсометрия и Отмывка. Попап не обрезан, текст читаем."
**Визуальные критерии:**
- Попап виден полностью
- Два чекбокса с подписями
- Текст читаем, контраст достаточный
- Попап не перекрывает критичные элементы карты
---
### TC-T-03 — Включение гипсометрии
**Тип:** ui
**Viewport:** desktop
**URL:** https://openclaw.mva154.duckdns.org/enduro/
**Шаги:**
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 3000
3. click: "#terrain-toggle"
4. wait: 500
5. click: "#terrain-hypso-cb"
6. wait: 3000
7. screenshot: "hypso-enabled"
8. check-visual: "Гипсометрия включена: на карте виден цветной полупрозрачный слой рельефа (зелёные/жёлтые/коричневые тона поверх базовой карты)."
**Визуальные критерии:**
- Виден цветной overlay поверх карты
- Карта под overlay всё ещё читаема
- Нет артефактов (чёрные/белые блоки)
---
### TC-T-04 — Включение отмывки (hillshade)
**Тип:** ui
**Viewport:** desktop
**URL:** https://openclaw.mva154.duckdns.org/enduro/
**Шаги:**
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 3000
3. click: "#terrain-toggle"
4. wait: 500
5. click: "#terrain-hypso-cb"
6. wait: 1000
7. click: "#terrain-hillshade-cb"
8. wait: 3000
9. screenshot: "hillshade-enabled"
10. check-visual: "Отмывка включена: видны тени рельефа (затемнение на склонах). Если зум < 10 — чекбокс hillshade должен быть disabled с подсказкой 'Зум 10+'."
**Визуальные критерии:**
- Если зум >= 10: видны тени на рельефе
- Если зум < 10: чекбокс hillshade неактивен, подсказка видна
- Нет чёрных/белых артефактов
---
### TC-T-05 — Попап закрывается по повторному клику
**Тип:** ui
**Viewport:** desktop
**URL:** https://openclaw.mva154.duckdns.org/enduro/
**Шаги:**
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 3000
3. click: "#terrain-toggle"
4. wait: 500
5. screenshot: "popup-open"
6. click: "#terrain-toggle"
7. wait: 500
8. screenshot: "popup-closed"
9. check-visual: "Попап закрылся после повторного клика на кнопку рельеф. Попап не виден на экране."
**Визуальные критерии:**
- Попап не виден на втором скриншоте
- Карта видна полностью без перекрытий
---
### TC-T-06 — Мобильный попап не обрезан
**Тип:** ui
**Viewport:** mobile
**URL:** https://openclaw.mva154.duckdns.org/enduro/
**Шаги:**
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 3000
3. click: "#terrain-toggle"
4. wait: 500
5. screenshot: "terrain-popup-mobile"
6. check-visual: "Попап terrain полностью виден на мобильном экране. Не обрезан снизу/справа. Чекбоксы достаточного размера для тапа (>44px)."
**Визуальные критерии:**
- Попап целиком в viewport
- Чекбоксы кликабельного размера
- Текст читаем на маленьком экране

View File

@@ -0,0 +1,148 @@
{
"timestamp": "2026-05-12T21:44:53.908Z",
"testFile": "TEST_CASES_UI_TERRAIN.md",
"results": [
{
"id": "TC-T-01",
"name": "Кнопка рельеф видна",
"viewport": "desktop",
"status": "completed",
"screenshots": [
"TC-T-01-desktop-terrain-btn-visible.png",
"TC-T-01-desktop-check-1778622228148.png"
],
"checks": [
{
"description": "Кнопка рельеф (иконка горы) видна в правой панели кнопок. Не обрезана, достаточного размера для тапа.",
"screenshot": "TC-T-01-desktop-check-1778622228148.png"
}
],
"errors": []
},
{
"id": "TC-T-01",
"name": "Кнопка рельеф видна",
"viewport": "mobile",
"status": "completed",
"screenshots": [
"TC-T-01-mobile-terrain-btn-visible.png",
"TC-T-01-mobile-check-1778622233547.png"
],
"checks": [
{
"description": "Кнопка рельеф (иконка горы) видна в правой панели кнопок. Не обрезана, достаточного размера для тапа.",
"screenshot": "TC-T-01-mobile-check-1778622233547.png"
}
],
"errors": []
},
{
"id": "TC-T-02",
"name": "Попап рельеф открывается",
"viewport": "desktop",
"status": "completed",
"screenshots": [
"TC-T-02-desktop-terrain-popup-open.png",
"TC-T-02-desktop-check-1778622239862.png"
],
"checks": [
{
"description": "Попап рельеф открылся. Видны два чекбокса: Гипсометрия и Отмывка. Попап не обрезан, текст читаем.",
"screenshot": "TC-T-02-desktop-check-1778622239862.png"
}
],
"errors": []
},
{
"id": "TC-T-02",
"name": "Попап рельеф открывается",
"viewport": "mobile",
"status": "completed",
"screenshots": [
"TC-T-02-mobile-terrain-popup-open.png",
"TC-T-02-mobile-check-1778622245753.png"
],
"checks": [
{
"description": "Попап рельеф открылся. Видны два чекбокса: Гипсометрия и Отмывка. Попап не обрезан, текст читаем.",
"screenshot": "TC-T-02-mobile-check-1778622245753.png"
}
],
"errors": []
},
{
"id": "TC-T-03",
"name": "Включение гипсометрии",
"viewport": "desktop",
"status": "completed_with_errors",
"screenshots": [
"TC-T-03-desktop-hypso-enabled.png",
"TC-T-03-desktop-check-1778622259991.png"
],
"checks": [
{
"description": "Гипсометрия включена: на карте виден цветной полупрозрачный слой рельефа (зелёные/жёлтые/коричневые тона поверх базовой карты).",
"screenshot": "TC-T-03-desktop-check-1778622259991.png"
}
],
"errors": [
"Click failed on \"#terrain-hypso-cb\": page.click: Timeout 5000ms exceeded.\nCall log:\n - waiting for locator('#terrain-hypso-cb')\n - lo"
]
},
{
"id": "TC-T-04",
"name": "Включение отмывки (hillshade)",
"viewport": "desktop",
"status": "completed_with_errors",
"screenshots": [
"TC-T-04-desktop-hillshade-enabled.png",
"TC-T-04-desktop-check-1778622280567.png"
],
"checks": [
{
"description": "Отмывка включена: видны тени рельефа (затемнение на склонах). Если зум < 10 — чекбокс hillshade должен быть disabled с подсказкой 'Зум 10+'.",
"screenshot": "TC-T-04-desktop-check-1778622280567.png"
}
],
"errors": [
"Click failed on \"#terrain-hypso-cb\": page.click: Timeout 5000ms exceeded.\nCall log:\n - waiting for locator('#terrain-hypso-cb')\n - lo",
"Click failed on \"#terrain-hillshade-cb\": page.click: Timeout 5000ms exceeded.\nCall log:\n - waiting for locator('#terrain-hillshade-cb')\n "
]
},
{
"id": "TC-T-05",
"name": "Попап закрывается по повторному клику",
"viewport": "desktop",
"status": "completed",
"screenshots": [
"TC-T-05-desktop-popup-open.png",
"TC-T-05-desktop-popup-closed.png",
"TC-T-05-desktop-check-1778622287510.png"
],
"checks": [
{
"description": "Попап закрылся после повторного клика на кнопку рельеф. Попап не виден на экране.",
"screenshot": "TC-T-05-desktop-check-1778622287510.png"
}
],
"errors": []
},
{
"id": "TC-T-06",
"name": "Мобильный попап не обрезан",
"viewport": "mobile",
"status": "completed",
"screenshots": [
"TC-T-06-mobile-terrain-popup-mobile.png",
"TC-T-06-mobile-check-1778622293523.png"
],
"checks": [
{
"description": "Попап terrain полностью виден на мобильном экране. Не обрезан снизу/справа. Чекбоксы достаточного размера для тапа (>44px).",
"screenshot": "TC-T-06-mobile-check-1778622293523.png"
}
],
"errors": []
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 KiB