auto-sync: 2026-05-06 01:10:01
This commit is contained in:
@@ -87,3 +87,90 @@
|
||||
- Mouse drag: важно вешать mousemove/mouseup на `document`, а не на `list` — иначе drag ломается при выходе курсора за пределы элемента
|
||||
- OSRM бэкенд не возвращает `legs` — только общий `distance_m`. Сегменты считаем на фронте через snap к геометрии
|
||||
- `streaming.mode: "progress"` — лучший режим для Telegram: нет промежуточных сообщений, только финальный ответ
|
||||
# 2026-05-05 — Дневник
|
||||
|
||||
## Enduro Trails — работа над прототипом
|
||||
|
||||
### Кнопка «Поделиться» → «Скачать GPX»
|
||||
- Слава попросил убрать кастомный диалог «Поделиться» (Telegram/WhatsApp)
|
||||
- Оставить только скачивание GPX через `downloadGPX()`
|
||||
- Иконка заменена на download-стрелку (стрелка вниз + линия) в том же стиле что и другие кнопки header
|
||||
- HTML уже обновлён: `onclick="downloadGPX()"`, иконка download
|
||||
- Из app.js удалены функции: `shareRoute`, `closeShareDialog`, `shareTelegram`, `shareWhatsApp`, `shareNative`
|
||||
- Из app.css удалены стили: `.share-dialog`, `.sd-*`, `#share-dialog`
|
||||
|
||||
### Drag-and-drop для точек маршрута
|
||||
- Слава попросил добавить иконку «перетаскивания» (grip) в каждый wl-item
|
||||
- Дев-агент добавил grip SVG (6 кружков) и touch-drag логику
|
||||
- Логика вынесена в `_initWaypointDragHandles(list)` с общими функциями `startDrag/moveDrag/endDrag`
|
||||
- **Проблема:** на десктопе не работало — дев сделал только touch-события
|
||||
- **Фикс:** добавлены mouse-события (mousedown/mousemove/mouseup) с `document`-level listeners для корректного завершения drag за пределами списка
|
||||
|
||||
### Баг: «Добавить точку» не работала в баре маршрутов
|
||||
- Причина: `addWaypointMode()` не устанавливала `routeMode = true`
|
||||
- При закрытом/свайпнутом sheet `routeMode` мог быть `false`
|
||||
- Клик на карте проверяет `if (!routeMode) return` — и игнорировал добавление
|
||||
- Фикс: в `addWaypointMode()` добавить `routeMode = true` перед установкой `addingWaypoint = true`
|
||||
|
||||
### Расстояние по маршруту между точками
|
||||
- Слава попросил показывать расстояние по маршруту (не по прямой) для каждого сегмента
|
||||
- Бэкенд не возвращает `legs` — только общий `distance_m`
|
||||
- Решение: функция `getRouteSegmentDistances()` — snap waypoints к геометрии маршрута, суммировать haversine по точкам геометрии
|
||||
- Проблема 1: сумма сегментов не совпадала с `route.distance_m` из-за неточного snap
|
||||
- Решение: масштабировать сегменты пропорционально чтобы сумма = `route.distance_m`
|
||||
- Проблема 2: при смене варианта маршрута расстояния не обновлялись
|
||||
- Фикс: добавить `renderWaypointsList()` в `selectRoute()` и `selectMiniRoute()`
|
||||
|
||||
### Дублированные сообщения в Telegram
|
||||
- Слава заметил что от меня приходят дублированные сообщения
|
||||
- Анализ: `streaming.mode: "partial"` шлёт промежуточные сообщения каждые 2 сек вместо edit
|
||||
- Попробовали `progress` — тоже дублировало (маппится в partial на Telegram)
|
||||
- Финальное решение: `streaming.mode: "off"` — только финальное сообщение, без превью
|
||||
- Конфиг обновлён, hot-reload применился (лог: `config hot reload applied`)
|
||||
|
||||
### Расстояние по маршруту — финальный фикс
|
||||
- Корневая проблема: `renderWaypointsList()` вызывается ДО завершения `debounceBuildRoute()` (async)
|
||||
- В момент рендера `routeResults` пустой → fallback на haversine по прямой (52 км вместо 104 км)
|
||||
- Фикс: добавить `renderWaypointsList()` сразу после `drawRouteResults()` — когда маршрут уже построен
|
||||
- Масштабирование сегментов оставлено: сумма сегментов = `route.distance_m` точно (diff=0 м)
|
||||
- Форматирование приведено к `toFixed(1)` везде для визуального совпадения
|
||||
|
||||
### voice-tts скилл — дублированные сообщения
|
||||
- `send_voice.sh` пытался отправить через `openclaw message send` И через `MEDIA:` директиву одновременно
|
||||
- Результат: в Telegram приходило сообщение с путём к файлу + голосовое
|
||||
- Фикс: убрать блок `openclaw message send` из скрипта, оставить только генерацию OGG
|
||||
- Доставка только через `MEDIA:` директиву в ответе ассистента
|
||||
|
||||
### Фаза 5 — редизайн фронтенда (завершена)
|
||||
- Убраны отладочные маркеры ✓/~ и console.log из app.js
|
||||
- Задеплоена чистая версия: SFTP → docker restart → docker cp (правильный порядок)
|
||||
- Документация обновлена: PROJECT.md, BRD_PHASE5.md
|
||||
|
||||
### Поиск точек маршрута (новая фича)
|
||||
- Слава попросил убрать верхний search bar и добавить поиск прямо в waypoints
|
||||
- BRD создан: `tasks/enduro-trails/BRD_WAYPOINT_SEARCH.md`
|
||||
- Dev реализовал: кнопка-лупа в каждом wl-item, inline Nominatim поиск, btn-theme перенесён в map-controls-r
|
||||
- Задеплоено успешно
|
||||
|
||||
### Онбординг маршрута + баги маркеров (Dev завершил)
|
||||
- Баг: метки и линейка отображались в верхнем левом углу → фикс: `.maplibregl-marker { position: absolute !important }`
|
||||
- Баг: подсказка «Тапни на карте» не видна → фикс: `#route-status { min-height: 20px }`
|
||||
- Фича: онбординг при активации маршрута — поле «Откуда?» при 0 точках, «Куда?» при 1 точке
|
||||
- Подсказка «или тапни на карте» видна в обоих состояниях
|
||||
|
||||
### Новые замечания Славы (в работе у Dev)
|
||||
1. Иконка колеса — заменить на нормальную мотокросс иконку
|
||||
2. Флажок финиша — чёрно-белая шахматная раскраска (финишный флаг)
|
||||
3. Спиннер в основном листе маршрута пока строится (как в мини-баре)
|
||||
4. Тёмная карта при тёмной теме — **в бэклог** (нет `style-light.json` на сервере, нужна отдельная задача)
|
||||
5. Автозум на маршрут после построения — плавный `fitBounds`
|
||||
6. Мини-бар перекрывает кнопки справа — исправить позиционирование
|
||||
|
||||
## Технические заметки
|
||||
- Web Share API требует HTTPS — на HTTP молча падает, даже на Android Chrome
|
||||
- Mouse drag: важно вешать mousemove/mouseup на `document`, а не на `list`
|
||||
- OSRM бэкенд не возвращает `legs` — только общий `distance_m`. Сегменты считаем на фронте через snap к геометрии
|
||||
- `streaming.mode: "off"` — только финальное сообщение в Telegram, без дублей
|
||||
- `deploy_static.js` сломан (cp до рестарта) — использовать inline node скрипт: restart → sleep 8s → docker cp
|
||||
- `style-light.json` отсутствует на сервере — тёмная тема карты требует отдельной задачи (MapLibre стиль)
|
||||
- Деплой: SFTP → docker restart → docker cp (порядок критичен — образ перезаписывает статику при рестарте)
|
||||
|
||||
@@ -98,6 +98,7 @@ docker restart prototype-enduro-trails-1
|
||||
| F-15 | "Народные треки" | OSM Traces, Wikiloc, Komoot, 4x4travel | ⏳ Бэклог | 8 |
|
||||
| F-16 | Тёмная тема + редизайн | Две темы (авто/светлая/тёмная), SunCalc, мобильный UI, drag-and-drop точек, расстояние по маршруту | ✅ Готово | 5 |
|
||||
| F-17 | PWA + офлайн | Service Worker, MBTiles, GPS-трекинг | ⏳ Бэклог | 7 |
|
||||
| F-18 | Светлая карта | Создать `style-light.json` — светлый стиль карты для светлой темы. Сейчас при светлой теме карта остаётся тёмной (`style-light.json` отсутствует) | ⏳ Бэклог | 5.1 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -126,7 +126,8 @@ html, body {
|
||||
#map-controls-r {
|
||||
position: fixed; right: 12px;
|
||||
bottom: calc(80px + env(safe-area-inset-bottom, 0px) + 12px);
|
||||
display: flex; flex-direction: column; gap: 8px; z-index: 200;
|
||||
display: flex; flex-direction: column; gap: 8px; z-index: 400;
|
||||
transition: bottom 0.2s ease;
|
||||
}
|
||||
.map-btn {
|
||||
width: 48px; height: 48px;
|
||||
@@ -629,7 +630,7 @@ body.has-map-mode #sheet-backdrop.visible { pointer-events: none; }
|
||||
/* ── Mini Route Bar ───────────────────────── */
|
||||
#sheet-route-mini {
|
||||
position: fixed;
|
||||
bottom: 72px; left: 0; right: 56px;
|
||||
bottom: 72px; left: 0; right: 0;
|
||||
height: 64px;
|
||||
background: var(--surface);
|
||||
border-top: 1px solid var(--border);
|
||||
|
||||
30
tasks/enduro-trails/prototype/static/app.js
vendored
30
tasks/enduro-trails/prototype/static/app.js
vendored
@@ -476,10 +476,26 @@ function createWaypointMarkerEl(index, total) {
|
||||
bg = '#0066ff'; label = String(index);
|
||||
}
|
||||
|
||||
el.innerHTML = `<svg width="28" height="36" viewBox="0 0 28 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 1C6.82 1 1 6.82 1 14C1 24 14 35 14 35C14 35 27 24 27 14C27 6.82 21.18 1 14 1Z" fill="${bg}" stroke="white" stroke-width="1.5"/>
|
||||
<text x="14" y="19" text-anchor="middle" font-family="system-ui,-apple-system,sans-serif" font-size="${label.length > 1 ? '9' : '11'}" font-weight="700" fill="white">${label}</text>
|
||||
</svg>`;
|
||||
if (label === 'F') {
|
||||
const uid = Math.random().toString(36).slice(2);
|
||||
el.innerHTML = `<svg width="28" height="36" viewBox="0 0 28 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="chk-${uid}" x="0" y="0" width="8" height="8" patternUnits="userSpaceOnUse">
|
||||
<rect width="4" height="4" fill="black"/>
|
||||
<rect x="4" y="0" width="4" height="4" fill="white"/>
|
||||
<rect x="0" y="4" width="4" height="4" fill="white"/>
|
||||
<rect x="4" y="4" width="4" height="4" fill="black"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<path d="M14 1C6.82 1 1 6.82 1 14C1 24 14 35 14 35C14 35 27 24 27 14C27 6.82 21.18 1 14 1Z" fill="url(#chk-${uid})" stroke="white" stroke-width="1.5"/>
|
||||
<text x="14" y="19" text-anchor="middle" font-family="system-ui,-apple-system,sans-serif" font-size="11" font-weight="700" fill="white" stroke="black" stroke-width="0.5">F</text>
|
||||
</svg>`;
|
||||
} else {
|
||||
el.innerHTML = `<svg width="28" height="36" viewBox="0 0 28 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 1C6.82 1 1 6.82 1 14C1 24 14 35 14 35C14 35 27 24 27 14C27 6.82 21.18 1 14 1Z" fill="${bg}" stroke="white" stroke-width="1.5"/>
|
||||
<text x="14" y="19" text-anchor="middle" font-family="system-ui,-apple-system,sans-serif" font-size="${label.length > 1 ? '9' : '11'}" font-weight="700" fill="white">${label}</text>
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
return el;
|
||||
}
|
||||
@@ -2126,12 +2142,18 @@ function showMiniRouteSheet() {
|
||||
if (!routeResults || routeResults.length === 0) return;
|
||||
updateMiniRouteCard();
|
||||
document.getElementById('sheet-route-mini').classList.add('visible');
|
||||
// Поднять кнопки карты над мини-баром (64px высота + 72px bottom + 8px отступ)
|
||||
const ctrl = document.getElementById('map-controls-r');
|
||||
if (ctrl) ctrl.style.bottom = '148px';
|
||||
initMiniRouteInteraction();
|
||||
}
|
||||
|
||||
function hideMiniRouteSheet() {
|
||||
const el = document.getElementById('sheet-route-mini');
|
||||
if (el) el.classList.remove('visible');
|
||||
// Вернуть кнопки на место
|
||||
const ctrl = document.getElementById('map-controls-r');
|
||||
if (ctrl) ctrl.style.bottom = '';
|
||||
}
|
||||
|
||||
function updateMiniRouteCard() {
|
||||
|
||||
Reference in New Issue
Block a user