Базовый образ `python:3.12-slim` не содержит `curl`, поэтому текущий
healthcheck `["CMD", "curl", "-f", ...]` всегда падает (`exec: "curl":
executable file not found`), и контейнер `enduro-trails-app-1` висит
в статусе `unhealthy` (≥31 час, FailingStreak 3762 при RestartCount 0),
несмотря на то что приложение исправно отвечает HTTP 200 на /api/health.
Заменяем healthcheck на python one-liner через stdlib `urllib.request`
(ADR-020). Изменения:
• docker-compose.yml, сервис app:
test: ["CMD", "python", "-c",
"import urllib.request,sys; sys.exit(0 if
urllib.request.urlopen(...timeout=3).status == 200 else 1)"]
+ start_period: 20s
interval/timeout/retries сохранены (30s / 5s / 3).
Внутренний urlopen(timeout=3) строго меньше внешнего healthcheck
timeout=5s (AC-07).
• Dockerfile НЕ меняется (никаких apt-get install curl/wget — BRD §6,
AC-04). Деплой без ребилда: `docker compose up -d app` достаточно.
• src/api/main.py НЕ меняется. Контракт /api/health сохранён (AC-08).
Покрытие:
- tests/static/test_healthcheck_compose.py — 10 тестов (ST-01..ST-07
+ защита от регресса по target URL / start_period / baseline params).
- tests/unit/test_healthcheck_oneliner.py — 6 тестов (UT-01..UT-03),
исполняют ровно ту же one-liner-команду через subprocess против
локального мок-HTTPServer (200/301/404/500/503) и неиспользуемого
порта. URL подменяется через `_retarget`, чтобы тестировать живой
код из compose, а не его копию.
ADR: docs/work-items/ET-015/06-adr/ADR-020-healthcheck-via-python-urllib.md
CHANGELOG: запись в [Unreleased] / Fixed.
Refs: ET-015
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
При открытии любого .bottom-sheet через openSheet() теперь принудительно
скрывается #terrain-popup и снимается .active с #terrain-toggle. Это
устраняет z-index конфликт (popup z=500 над sheet z=400) и убирает
anti-pattern «два меню одновременно» на desktop без правки CSS-стека
(marker-dialog z=500, search-panel, ruler-info — без регрессий).
Реализация — Вариант A из ADR-019: helper closeTerrainPopup() + один
вызов первой строкой в openSheet() после null-check. Для других sheets
(sheet-route, sheet-recon, sheet-scenic, sheet-link, sheet-gpx) вызов
безопасный no-op, REQ-F-06 выполняется автоматически.
Тесты:
- tests/unit/sheet_popup.test.js — 8 behavioral JS unit-тестов
(TC-U-02, REQ-F-04, REQ-F-06 + ребра closeTerrainPopup).
- tests/unit/test_sheet_popup.py — pytest-обёртка: статические проверки
app.js (порядок вызовов в openSheet, маркеры блока), охранные тесты
что z-stack не тронут и что gps_tracks.js/index.html не правились.
Refs: ET-014
ADR: docs/work-items/ET-014/06-adr/ADR-019-terrain-popup-yields-to-sheet.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Понижаем UI-минзум hillshade с 10 до 9 и переводим raster-paint
обоих terrain-слоёв в zoom-aware форму через MapLibre interpolate.
На z9-z11 — пик opacity/contrast, чтобы рельеф читался как на z8;
на z12-z14 — возврат к исходным значениям (регрессия по AC-10).
TRI на z8 остаётся 0.70 (регрессия по AC-06), пик 0.80-0.85 на z9-z11.
Изменения:
- src/web/app.js: добавлены HILLSHADE_PAINT и TRI_PAINT; applyTerrainLayer
расширена для поддержки object-paint (обратно-совместимо); порог
updateHillshadeAvailability понижен до 9; вызовы для hillshade переведены
на minzoom=9.
- src/web/index.html: hint обновлён с «Зум 10+» на «Зум 9+».
- tests/unit/test_terrain_paint.py: 17 тестов покрытия zoom-stops, контракта
applyTerrainLayer и регрессий (UT-PAINT-*, UT-REG-*).
- tests/integration/test_terrain_z9_tiles.py: smoke /terrain endpoint на
z9-z11 + кэш-заголовки (IT-TILE-*).
Backend, тайлы на диске, конфиги, стили — без изменений.
Refs: ET-013
ADR: ADR-017
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12-review.md (REQUEST_CHANGES, attempt 2/3) flagged 6 must-fix items
in the analysis/architecture artefacts plus matching bugs that had
already leaked into the committed implementation. This patch lands
both: documents corrected, code aligned with corrected specs, tests
updated.
P1-1: TRZ/ADR/Data/Risks referenced fictional layer ids
(`trails-grade1..5-halo-satellite`, `paths-bridleway-halo-satellite`).
Actual style*.json has only `trails-track-halo-satellite` and
`trails-path-bridleway-halo-satellite`; grade differentiation lives
inside one `match` expression on `tracktype` within `trails-track`.
Docs rewritten to operate on real ids.
P1-2: POI labels contrast was broken — spec changed only halo-color
to black, leaving `text-color: #333333` (light theme baseline)
unreadable over the new black halo. Code+docs now switch BOTH
`text-color` (-> `#ffffff` on satellite) AND halo together, with
per-theme baselines (`#333333` light / `#e0e0e0` dark) restored on
return to Schematic.
P1-3: BRD §5 hillshade risk said «hillshade auto-disabled on
satellite», contradicting TRZ/ADR/AC. BRD wording aligned: hillshade
keeps working over satellite; visual check is AC-04.
P1-4: background-color had four divergent sources (`#1a1a1a`,
`#2a2a2a`, `#1a1a2e`, `#f0ede6`), incl. an inverted-theme typo and a
baseline `#1a1a1a` that didn't match the actual `style-dark.json:28`
value `#1a1a2e`. Settled on ADR-004's single-constant model: `#2a2a2a`
on satellite for both themes; on Schematic restore per-theme baselines
`#f0ede6` (light) / `#1a1a2e` (dark). `_applyBackgroundForSatellite`
fixed accordingly.
P1-5: app.js already had `layerState.basemap` and `toggleLayer
('basemap')` (legacy «Базовая карта» switch). Neither TRZ nor ADR
specified the interaction. Added save&restore contract: on entering
Satellite save `layerState.basemap` to `_savedBasemapState` and
force-hide `osm-base`; on returning to Schematic restore osm-base
visibility from the saved value. CSS hook `body.satellite-active
#btn-basemap { display:none }` keeps the user from trying to enable
a hybrid mode (out of scope, BRD §3). TRZ §5.6, ADR-004 §8.
P1-6: `restoreTrailsState()` and `onTrailsCheckbox()` only managed
visibility of `trails-track` / `trails-path-bridleway`, leaving
their halo-underlay siblings as «phantom» halos when the user
unchecked grunты/тропы under Satellite. Introduced
`_applyTrailHaloVisibility(map, base)` reading checkbox state from
DOM; called from `onTrailsCheckbox`, `restoreTrailsState`, and both
branches of `applyBaseLayer`. Rule: halo visible ⇔ (base ===
satellite) AND (checkbox ON). TRZ §5.7, ADR-004 §9.
Docs bumped: BRD v2, TRZ v2, AC v2, Data v2, Risks v2; ADR-004
получает «Ревизии»-секцию (status remains accepted — only editorial
fixes, no decision change).
Tests:
- tests/unit/base_layer.test.js: rewritten 2 background-color
assertions (#1a1a1a expectation removed), added 6 new tests for
P1-2 / P1-4 (POI text-color per-theme baselines, single satellite
bg #2a2a2a, baseline restore on Schematic).
- All 33 JS unit tests + 22 pytest static checks green.
- Full pytest suite: 76 passed (excluding pre-existing
shapely-import skipped collection in tests/unit/test_health.py).
Refs: ET-007
Review: docs/work-items/ET-007/12-review.md (P1-1..P1-6)
ADR: docs/work-items/ET-007/06-adr/ADR-004-satellite-base-layer.md (rev. 2026-05-31)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Правки по код-ревью ET-006 (docs/work-items/ET-006/12-review.md):
- P1-1: trackStats считал min/max высот через Math.min/max.apply — на
треках в сотни тысяч точек это бросало RangeError и валило загрузку
файла (нарушение REQ-NF-01). Расчёт переписан на однопроходный
аккумулятор (makeStatsAccumulator/accumulatePoint/finalizeStats)
без apply.
- P2-1: статистика и профиль высот учитывали только tracks[0].
Добавлены aggregateStats() и buildFileProfileSamples() — сводка и
профиль теперь охватывают все треки файла (REQ-F-09, AC-02).
- P2-2: расчёт статистики на async-пути парсинга вынесен в чанковый
trackStatsChunked() — соответствие букве ADR-003 §2.
- P3-1: ось и tooltip профиля высот форматируют расстояние через
formatKm() — согласование с выбором км/мили из ET-005.
- P3-2: childText() переименована в firstTagText() — имя соответствует
фактическому поведению (поиск по всем потомкам).
- P3-4: убран дублирующийся 'use strict'.
Добавлены регрессионные unit-тесты: большой трек без падения,
эквивалентность trackStatsChunked синхронному trackStats (в т.ч. на
треке длиннее размера чанка), агрегация статистики и профиля по
многотрековому файлу.
Refs: ET-006
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Добавляет сегментированный toggle км/мили в попап рельефа. Новый модуль
src/web/units.js — единственный источник истины по выбору единицы, её
персистентности (localStorage: distance_unit, дефолт km) и форматированию
отображаемых расстояний (Units.formatDistance).
Все места форматирования в app.js переведены на централизованный
форматтер; пересчёт всех видимых расстояний выполняет единый оркестратор
onUnitChange по событию unitchange (карточки маршрутов, лист точек,
линейка, масштабная линейка, связка, «красивый» маршрут).
Экспорт GPX и параметры построения маршрута остаются метрическими
(риск R6). units.js подключается строго перед app.js (риск R7).
Refs: ET-005
Adds a «POI» checkbox to the terrain popup that toggles the
poi-circles and poi-labels layers via map.setLayoutProperty. The
choice is persisted in localStorage (key `poi-visible`) and restored
on page load and after style changes, kept consistent with the
runtime layerState.poi per ADR-0001.
Tests: behavioral JS unit tests (TP-01..TP-04) run via `node --test`,
wrapped by tests/unit/test_poi_toggle.py with static structure checks
so they execute under the existing `pytest tests/` CI step.
Refs: ET-002
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>