22 KiB
22 KiB
type, work_item_id, title, version, status, created_at, authors
| type | work_item_id | title | version | status | created_at | authors | |
|---|---|---|---|---|---|---|---|
| tech-risks | ET-014 | Технические риски — ET-014: Z-index фикс — terrain-popup уступает sheet'у | 1 | approved | 2026-06-04 |
|
Технические риски — ET-014
Технические риски фикса z-index конфликта #terrain-popup ↔
#sheet-gps-filters. Бизнес-риски — в BRD §9 (R1..R3). Шкала:
вероятность (Н/С/В) × влияние (Н/С/В).
R-T-1 — closeTerrainPopup() падает на ранней загрузке, когда DOM не готов
- Описание: Если по какому-то race condition
openSheet()вызывается до того, как#terrain-popup/#terrain-toggleпоявятся в DOM,getElementByIdвернётnull. Helper защищён ранним возвратом (if (!popup || popup.style.display === 'none') return;), но еслиbtnnull, аpopupесть —btn.classList.removeупадёт. - Вероятность / Влияние: Н / Н.
- Митигация:
- Архитектурное решение (ADR-019 §«Решение»): в helper'е
проверка
if (btn) btn.classList.remove('active');. - DOM-инвариант:
#terrain-popupи#terrain-toggle— оба статически прописаны вindex.html(строки ~43 и в#map-controls-r). Они существуют сразу после парсинга HTML, ещё до выполненияapp.js(который грузится сdefer). Реалистичная вероятность null — околонулевая. - Acceptance гейт: AC-09 (TC-UI-05) — все 5 sheet'ов открываются последовательно, helper срабатывает 5 раз без ошибок.
- Архитектурное решение (ADR-019 §«Решение»): в helper'е
проверка
R-T-2 — Двойной removeEventListener на closeTerrainOnOutside
- Описание: При сценарии «открыт popup → клик по ссылке
Фильтры… →
openSheet(...)вызвалcloseTerrainPopup()→removeEventListenerсработал» — а затем пользователь закрывает sheet и снова открывает popup,addEventListenerповесит listener заново. Но еслиcloseTerrainOnOutsideбыл вызван иначе (например, через клик по карте в момент закрытия sheet'а — гипотетически), то оба removeEventListener'а отработают над одним и тем же handler'ом. - Вероятность / Влияние: Н / Н.
- Митигация:
- DOM-спека:
removeEventListenerна отсутствующий handler — no-op (silent). Никаких exception'ов. - Архитектурное решение: helper идемпотентен по построению:
if (popup.style.display === 'none') return;— повторный вызов при уже закрытом popup'е выходит мгновенно, без вызововremove*.
- DOM-спека:
R-T-3 — Регрессия открытия других sheet'ов (sheet-route и пр.)
- Описание: Изменение
openSheetзатрагивает 6 sheet'ов: route, recon, scenic, link, gpx, gps-filters. Если новый вызовcloseTerrainPopup()имеет побочный эффект для случая «popup закрыт», это сломает все 5 «здоровых» sheet'ов. - Вероятность / Влияние: Н / С.
- Митигация:
- Архитектурное решение (ADR-019 §«Решение»): helper строго
no-op'ит при
popup.style.display === 'none'(ранний выход первой строкой после null-check). При открытии sheet-route/recon/scenic/ link/gpx popup гарантированно закрыт (нет UI-пути открыть его до клика на#terrain-toggle, который не задействован в этих сценариях). - Acceptance гейт: AC-09 (TC-UI-05) — открытие всех 5 «здоровых» sheet'ов через тулбар. Обязательный гейт перед merge.
- Sanity unit-тест (опциональный): статический grep, что в
openSheetровно один вызовcloseTerrainPopup(не два, не забытый).
- Архитектурное решение (ADR-019 §«Решение»): helper строго
no-op'ит при
R-T-4 — display:none ломает положение popup'а после повторного открытия
- Описание:
toggleTerrainPopup()используетpopup.style.display !== 'none'для определения текущего состояния (app.js:2775). Если мы скрыли popup черезcloseTerrainPopup(), при следующем клике на#terrain-toggleфункция правильно определит «закрыт» и откроется снова. Но если осталась inlinetop/right, popup появится в старой позиции — может быть некорректно при resize окна между открытиями. - Вероятность / Влияние: Н / Н.
- Митигация:
- Архитектурное решение:
toggleTerrainPopup()(app.js:2779- 2786) каждый раз пересчитываетtopиrightизbtn.getBoundingClientRect()при открытии. Никакой stale-позиции не остаётся. - Acceptance гейт: AC-07, AC-08 — повторное открытие popup'а после закрытия sheet'а проверяется.
- Архитектурное решение:
R-T-5 — Marker-dialog/search-panel/ruler-info регрессии при правке openSheet
- Описание:
#marker-dialog(z=500),#search-panel(z=600),#ruler-info(z=600) не относятся к.bottom-sheet. Они открываются не черезopenSheet, а через свои обработчики (tb-marker/tb-search/tb-ruler). Если наша правка случайно затронула общий код пути этих виджетов — регрессия. - Вероятность / Влияние: Н / С.
- Митигация:
- Архитектурное решение (ADR-019 §«Что НЕ меняется»): правка
локализована только в
openSheet(вызывается только для.bottom-sheet). z-index стек не трогается → marker-dialog, search-panel, ruler-info остаются на своих местах в стеке. - Acceptance гейт: AC-10, AC-11, AC-12 (TC-UI-08 + ручные проверки search-panel и ruler-info).
- REQ-NF-03: прямое отражение этого риска в TRZ.
- Архитектурное решение (ADR-019 §«Что НЕ меняется»): правка
локализована только в
R-T-6 — Старый клиент в кэше браузера получает старый багованный app.js
- Описание: Пользователь с открытой вкладкой неделю назад имеет
закэшированный старый
app.jsбезcloseTerrainPopup. Service worker — не настроен в MVP. До reload браузер не дёрнет свежий код. - Вероятность / Влияние: С / Н.
- Митигация:
- Архитектурное решение:
src/web/index.htmlгрузитapp.jsнапрямую. nginx + стандартныйCache-Controlна*.js(не immutable). При reload браузер делает conditional GET → 200 (свежий) или 304. - Backwards compat: старый кэшированный клиент с багом продолжает работать в багованном режиме, никаких 4xx/5xx нет. Никакого hard-reload не требуется — обычный F5 / pull-to-refresh подхватит fix.
- Долгосрочная митигация: PWA / SW (PH-9) введёт правильную инвалидацию.
- Архитектурное решение:
R-T-7 — Пользователь ожидает «возврат к panel слоёв» после закрытия sheet'а
- Описание: BRD R2 явно описан: «пользователь может удивиться, что панель слоёв сама закрылась». После закрытия фильтров пользователь оказывается на карте, а не в panel слоёв. Кому-то это может показаться «прыжок UX».
- Вероятность / Влияние: С / Н.
- Митигация:
- Архитектурное решение (ADR-019 §A): BRD R2 разрешает такое поведение: «панель слоёв — точка входа в фильтры, после закрытия фильтров пользователь возвращается к карте». Это решение оператора, зафиксировано в BRD.
- UX-нота для test-report: оператор фиксирует свои наблюдения
в
13-test-report.md. - Fallback (если оператор передумает): в
closeAllSheets/closeSheet('sheet-gps-filters')дополнительно перезапускатьtoggleTerrainPopup— но это существенное расширение scope и требует отдельной задачи (ET-014.1 или новый work-item).
R-T-8 — Свайп фильтров вниз — popup не возвращается
- Описание: Та же концептуальная проблема, что R-T-7, но через жест свайпа. Пользователь свайпом закрывает sheet, видит карту, а не panel слоёв.
- Вероятность / Влияние: С / Н.
- Митигация:
- Та же что R-T-7: BRD R2 это разрешает.
- Acceptance гейт: AC-03 включает чекбоксы внутри sheet'а; свайп
не тестируется отдельно (он = клик
✕поведенчески).
R-T-9 — В будущем кто-то откроет sheet с явным намерением «не закрывать popup»
- Описание: Пока такого сценария нет (BRD §8 допускает, что
единственная точка входа в
sheet-gps-filtersиз popup'а — это «Фильтры…»). Но если завтра появится «открыть мини-фильтр из popup'а, оставив popup открытым», текущее общее правилоopenSheet → closeTerrainPopupзаблокирует такой сценарий. - Вероятность / Влияние: Н / Н.
- Митигация:
- Архитектурное решение (ADR-019 §«Технический долг» TD-2):
при появлении такого сценария — расширение
openSheet(id, opts)объектом опций с флагомkeepPopup: true. Сейчас — YAGNI.
- Архитектурное решение (ADR-019 §«Технический долг» TD-2):
при появлении такого сценария — расширение
R-T-10 — eslint падает на новой функции из-за code style
- Описание: Если в проекте настроен
eslintс правилами наprefer-const,func-style,no-implicit-globals— новаяfunction closeTerrainPopup()может не пройти конкретные правила стиля. - Вероятность / Влияние: Н / Н.
- Митигация:
- Архитектурное решение: другие helper'ы в
app.js(openSheet,closeSheet,closeAllSheets,closeTerrainOnOutside) объявлены черезfunction name()без проблем — значит, eslint их пропускает. - Acceptance гейт:
make lintзелёный (часть DoD).
- Архитектурное решение: другие helper'ы в
R-T-11 — Playwright TC-UI-* нестабильны на test-среде из-за тайминга
- Описание: TC-UI-01..TC-UI-08 используют фиксированные
wait(300-600 мс) после кликов. На загруженной test-среде анимация открытия sheet'а (transition: transform 0.3s) может не успеть завершиться, скриншот будет «полу-открытым». - Вероятность / Влияние: С / Н.
- Митигация:
- Архитектурное решение: TC-UI-* — операторские, не CI-blocking
(см.
04b-ui-test-cases.md). Оператор делает финальную приёмку. - Tuning: если CI-прогон нестабилен — поднимать wait'ы до 800 мс (мажорная анимация = 300 мс + слабая retry).
- Это вне scope ADR-019.
- Архитектурное решение: TC-UI-* — операторские, не CI-blocking
(см.
R-T-12 — В будущем z-index у #sheet-backdrop или .bottom-sheet поднимут до >500 без знания о ADR-019
- Описание: Кто-то решит «давайте сделаем sheets z=510» (Вариант B), не зная, что мы выбрали Вариант A. Тогда правка не сломает ничего (она лишь подкрепит fix), но логика становится двойной: и popup закрывается, и z-index хитрый. Сложнее понимать систему.
- Вероятность / Влияние: С / Н.
- Митигация:
- Архитектурное решение (ADR-019 §«Альтернативы»): Вариант B зафиксирован как отклонённый. Если кто-то будет менять z-index, он прочитает ADR-индекс и увидит запись.
- Прецедент: комментарий в коде
app.js:// ET-014: terrain-popup yields to any opening sheet (see ADR-019).
R-T-13 — Десктоп: после закрытия фильтров пользователь не видит ни popup'а, ни фильтров, ни panel слоёв
- Описание: На desktop backdrop скрыт media-query
(
app.css:543: #sheet-backdrop { display: none; }). Sheet занимает слева ~380 px. После закрытия sheet'а пользователь видит чистую карту. Никаких «фантомных» элементов — но и контекста, где он только что был, нет. - Вероятность / Влияние: Н / Н.
- Митигация:
- Архитектурное решение: это специально так — BRD §3 «после закрытия пользователь возвращается к карте». На desktop нет визуальной потери (карта всегда видна, sheet был сбоку).
- Acceptance гейт: AC-02, AC-04.
R-T-14 — Регрессия повторного открытия popup'а с уже выставленной inline-позицией
- Описание: При закрытии через
closeTerrainPopup()мы выставляемpopup.style.display = 'none', но не сбрасываемpopup.style.topиpopup.style.right. При следующем открытии черезtoggleTerrainPopupзначения top/right пересчитываются, поэтому стейл не страшен. Но если кто-то в будущем добавит ветку «открыть popup без пересчёта позиции» — может сработать на остатках. - Вероятность / Влияние: Н / Н.
- Митигация:
- Архитектурное решение:
toggleTerrainPopup(app.js:2779- 2786) безусловно пересчитываетtop/rightпри каждом открытии. - Тест: AC-08 (TC-UI-07) — popup закрывается кликом вне, потом открывается заново; проверка визуальной корректности.
- Архитектурное решение:
R-T-15 — Сценарий «открыть фильтры, прокрутить sheet вниз и обратно к popup»
- Описание: Пользователь открыл фильтры, popup закрылся. Если бы popup остался в DOM-tree «фоном» (например, при z-index решении), можно было бы свайпом или ESC вернуться к нему. После ET-014 этого пути нет.
- Вероятность / Влияние: Н / Н.
- Митигация:
- Архитектурное решение: этот сценарий не был доступен и до
ET-014 (popup
display:blockне позволял прокрутить «к popup'у» — он и так был видим). UX не теряет ничего.
- Архитектурное решение: этот сценарий не был доступен и до
ET-014 (popup
R-T-16 — Service worker в будущем (PH-9) перехватит app.js
- Описание: Когда PH-9 (PWA) введёт SW, он начнёт кэшировать
app.jsв Cache Storage. Деплой ET-014 потребует cache-busting стратегии (?v=, hash в имени файла илиclients.claim()+skipWaiting()в SW). - Вероятность / Влияние: Н / Н.
- Митигация:
- PH-9 — отдельный work-item. К моменту его реализации ET-014 уже
давно в test/prod, новый SW при первой установке возьмёт свежий
app.js. Никаких специальных действий для ET-014 не нужно.
- PH-9 — отдельный work-item. К моменту его реализации ET-014 уже
давно в test/prod, новый SW при первой установке возьмёт свежий
Сводная таблица
| # | Риск | Вер | Влиян | Митигация (тип) |
|---|---|---|---|---|
| R-T-1 | closeTerrainPopup падает на ранней DOM-загрузке |
Н | Н | null-check в helper; DOM-инвариант; AC-09 |
| R-T-2 | Двойной removeEventListener |
Н | Н | DOM-спека = no-op; идемпотентность helper'а |
| R-T-3 | Регрессия открытия 5 «здоровых» sheet'ов | Н | С | Ранний выход no-op; AC-09 = обязательный гейт |
| R-T-4 | Stale top/right у popup'а после reopen |
Н | Н | toggleTerrainPopup пересчитывает каждый раз; AC-07 |
| R-T-5 | Marker-dialog/search-panel/ruler-info регрессия | Н | С | Локализация правки; AC-10/AC-11/AC-12 = REQ-NF-03 |
| R-T-6 | Закэшированный старый app.js у пользователей |
С | Н | Conditional GET (If-Modified-Since); backwards compat |
| R-T-7 | UX-удивление «panel слоёв сама закрылась» | С | Н | BRD R2 разрешает; test-report фиксирует |
| R-T-8 | Свайп вниз — popup не возвращается | С | Н | То же что R-T-7 |
| R-T-9 | Будущий сценарий «открыть sheet, не закрывая popup» | Н | Н | YAGNI; TD-2 в ADR-019 |
| R-T-10 | eslint падает на новой функции |
Н | Н | Существующий стиль function name() принят |
| R-T-11 | Playwright TC-UI нестабильны по таймингу | С | Н | Операторская приёмка; tuning wait'ов |
| R-T-12 | Будущий developer не знает про ADR-019, поднимет z-index | С | Н | ADR в индексе; комментарий в коде |
| R-T-13 | Desktop: пустая карта после закрытия — нет контекста | Н | Н | Specified by BRD §3 |
| R-T-14 | Stale inline-позиция popup'а | Н | Н | Пересчёт в toggleTerrainPopup каждый раз |
| R-T-15 | «Возврат к popup'у» через свайп невозможен | Н | Н | Сценарий не существовал и раньше |
| R-T-16 | PH-9 (SW) перехватит app.js |
Н | Н | Не задача ET-014; SW при первой установке свежий |
Связанные документы
01-brd.md§4 BR-01..BR-06, §9 R1..R3 (бизнес-риски пересекаются)02-trz.md§2.1 REQ-F-01..REQ-F-07, §2.2 REQ-NF-01..REQ-NF-05, §3 (варианты)03-acceptance-criteria.mdAC-01..AC-14 (все гейты)04b-ui-test-cases.mdTC-UI-01..TC-UI-0806-adr/ADR-019-terrain-popup-yields-to-sheet.md§«Решение», §«Последствия», §«Технический долг»07-infra-requirements.md§6 (deploy procedure), §7 (мониторинг)08-data-requirements.mddocs/work-items/ET-013/10-tech-risks.md— образец «calibration risks» документа (наследие)