---
type: adr
work_item_id: ET-002
adr_id: adr-0001
title: "ADR-0001: Управление видимостью POI — клиентское решение на localStorage"
status: accepted
created_at: 2026-05-21
authors:
  - "agent:architect"
supersedes: []
superseded_by: []
labels: []
---

# ADR-0001 — Управление видимостью POI: клиентское решение на localStorage

## Статус

Accepted

## Контекст

ET-002 добавляет в попап кнопки «Рельеф» (`terrain-popup`) чекбокс «POI»,
позволяющий пользователю скрывать маркеры POI (слои `poi-circles`,
`poi-labels`), чтобы не загромождать карту при планировании маршрута.

Существующее состояние кодовой базы (`src/web/app.js`):

- объект `layerState = { tracks, paths, poi, basemap }` — внутренняя модель
  видимости слоёв (строка 377), `poi: true` по умолчанию;
- карта групп слоёв `poi: ['poi-circles', 'poi-labels']` (строка 381);
- функция `toggleLayer(group)` уже переключает видимость через
  `map.setLayoutProperty(..., 'visibility', ...)` (строки 385–389), но
  завязана на кнопку-тулбар (`btn.classList.toggle('active', ...)`), а не на
  чекбокс попапа;
- сложился устойчивый паттерн персистентности в `localStorage` со
  значениями `'1'`/`'0'`: ключи `terrain-hillshade`, `terrain-tri`,
  `trails-track`, `trails-path`, `enduro-theme-mode`, `MARKERS_KEY`.

Backend (FastAPI) и БД (SQLite/Spatialite) к видимости POI отношения не
имеют — POI отдаются как часть тайлов/источника, видимость регулируется
исключительно на уровне рендера MapLibre.

## Рассматриваемые варианты

### Вариант A — Клиентское решение: `setLayoutProperty` + `localStorage` (выбран)

Видимость переключается через `map.setLayoutProperty()` для слоёв
`poi-circles` и `poi-labels`; состояние хранится в `localStorage` под
ключом `poi-visible`; `layerState.poi` остаётся единственным источником
истины в рантайме.

- **Плюсы:** нулевые изменения backend/БД/инфраструктуры; переключение
  мгновенное (тайлы не перезагружаются — слой остаётся в источнике,
  меняется только `layout.visibility`); полностью консистентно с уже
  существующими чекбоксами попапа и принципом «минимум зависимостей».
- **Минусы:** настройка не синхронизируется между устройствами/браузерами
  (для данной фичи это приемлемо и зафиксировано как Out of scope в BRD).

### Вариант B — Хранение настройки на backend (профиль пользователя)

Видимость POI сохраняется через новый API-эндпоинт в SQLite.

- **Плюсы:** синхронизация между устройствами.
- **Минусы:** требует модели пользователя/сессий (в проекте отсутствует),
  новый эндпоинт, миграцию БД, сетевой round-trip — несопоставимо
  избыточно для одного UI-флага. Противоречит принципам BRD (минимум
  зависимостей, нет необходимости в server-state).

### Вариант C — Удаление/повторное добавление слоёв POI

Скрытие через `map.removeLayer()`, показ через `map.addLayer()`.

- **Плюсы:** нет «невидимых» слоёв в стиле.
- **Минусы:** дороже по производительности, риск рассинхрона порядка
  слоёв и обработчиков событий (`map.on('click', 'poi-circles', ...)`,
  строка 1515), усложняет код. `setLayoutProperty` — штатный механизм
  MapLibre именно для этого сценария.

## Решение

Принимается **Вариант A**.

1. **Видимость** слоёв `poi-circles` и `poi-labels` переключается через
   `map.setLayoutProperty(layerId, 'visibility', 'visible' | 'none')`.
2. **Персистентность** — `localStorage`, ключ `poi-visible`, значения
   `'1'` / `'0'`, в соответствии с уже сложившейся конвенцией проекта.
3. **Источник истины в рантайме** — `layerState.poi`. Обработчик
   `onPoiCheckbox()` обязан синхронно обновлять `layerState.poi`,
   `localStorage` и `layout.visibility` обоих слоёв.
4. **Без дублирования логики.** Логика установки видимости группы POI
   должна переиспользовать существующую карту групп слоёв
   (`poi: ['poi-circles','poi-labels']`). Допускается выделение общего
   приватного хелпера (например `applyPoiVisibility(visible)`),
   вызываемого как из `onPoiCheckbox()`, так и из восстановления
   состояния при загрузке. `toggleLayer('poi')` (тулбар-кнопка) и
   `onPoiCheckbox()` (чекбокс попапа) должны оставаться консистентны
   через общий `layerState.poi` — недопустимо, чтобы кнопка и чекбокс
   показывали разное состояние.
5. **Backend, БД, API, инфраструктура — без изменений.** Состав
   компонентов C4 не меняется, обновление C4-диаграмм не требуется.

## Последствия

### Положительные

- Изменение полностью клиентское: deploy без миграций и без изменения
  состава контейнеров.
- Переключение < 50 мс (REQ-NF-01) — тайлы не перезагружаются.
- Конвенция `localStorage` остаётся единообразной — ниже когнитивная
  нагрузка при поддержке.

### Отрицательные / ограничения

- Настройка локальна для браузера: при очистке `localStorage` или смене
  устройства сбрасывается на дефолт (POI включены). Принято осознанно.
- В стиле карты остаются слои с `visibility: none` — это штатно для
  MapLibre и не влияет на производительность.

### Технический долг

- `toggleLayer()` и `onPoiCheckbox()` частично дублируют намерение
  «переключить группу слоёв». Если в будущих фазах появятся ещё
  popup-чекбоксы для слоёв, стоит унифицировать тулбар-кнопки и
  popup-чекбоксы в единый контроллер слоёв. На ET-002 — вне scope.

## Классификация изменения

Minor change. Новые сервисы/БД/эндпоинты не вводятся, состав компонентов
не меняется. Лейбл `arch:major-change` **не требуется**. Обязательного
архитектурного approve не требуется.

## Связанные документы

- `docs/work-items/ET-002/01-brd.md`
- `docs/work-items/ET-002/02-trz.md`
- `docs/work-items/ET-002/03-acceptance-criteria.md`
- `docs/work-items/ET-002/07-infra-requirements.md`
- `docs/architecture/README.md`
