Skip to content

02. Реестр подтверждённых находок

Сводка

ПриоритетOpenСмысл
P00Crash/data loss/system-wide block не обнаружены
P14Два mobile collision и два modal/layering keyboard defect
P29A11y, viewport, motion, stacking, theme и test coverage
P31 пакетCode hygiene и неподтверждённые риски для отдельной triage

Release gate остаётся красным, пока открыт хотя бы один P1. P2 с нормативным a11y impact желательно закрыть до публичного релиза, но они не должны задерживать исправление геометрических и modal blockers.

P1 — release blockers

MAP-LAYOUT-001 — Top toolbar закрывает MapLibre controls на narrow viewport

  • Статус: Open / Confirmed
  • Приоритет: P1
  • Классификация: responsive interaction + focus/hit-area occlusion
  • Среда: 320×844, 360×844, 375×844, 390×844, частично 412×844; Chromium; обе темы должны считаться затронутыми до retest
  • Затронутые состояния: как минимум M01 IDLE и M03 DRAW; top zone общая для других режимов
  • Фактическое поведение: первый ряд toolbar начинается с x=12 и имеет ширину 376.7px. Zoom In MapLibre находится справа сверху. Площадь их пересечения — 1108.4 px² на ширинах 320–390 и 707.4 px² на 412. Element hit-test в центре Zoom возвращает toolbar child, а не Zoom.
  • Дополнительная проблема: на ширинах 320/360/375 top row выходит за viewport; Color заканчивается на x=385.7.
  • Ожидание: primary toolbar и MapLibre navigation одновременно видимы, focus-visible и pointer-reachable; ни один обязательный control не лежит под другим.
  • Вероятный источник: FloatingToolbar.module.css, позиция MapLibre controls в MapCanvas, отсутствие общего layout contract для top-left/top-right зон.
  • Не принимать за корень: координата отдельной кнопки внутри горизонтально scrollable tools row. Page-level scrollWidth равен viewport из-за intentional overflow containment.
  • Минимальное направление fix: зарезервировать явную top-right safe zone либо переместить MapLibre control group на mobile; уменьшить/перестроить первый ряд так, чтобы он помещался в 320px без скрытия обязательных controls.
  • Regression guard: geometry assertion для 320/360/375/390/412 проверяет viewport containment и intersectionArea(toolbar, zoom) === 0; hit-test центра Zoom возвращает сам control/его child.
  • Exit criteria: PASS в portrait и 844×390 landscape, dark/light, fine/coarse pointer; keyboard focus ring не скрыт; toolbar функции остаются доступны.

MAP-LAYOUT-002 — Changelog перекрывает действия mobile drawers

  • Статус: Fixed
  • Приоритет: P1
  • Классификация: responsive collision + hit target occlusion
  • Среда: 320×568, 390×844, 412×915, 844×390 (Chromium Playwright)
  • Затронутые состояния: Loop drawer, Route Builder/Search drawer, POI drawer
  • Решение: Кнопка Changelog скрывается на мобильных viewports при открытии любой drawer-панели (Search, Loop, POI). На десктопе поведение не изменилось.
  • Verification: Успешно протестировано в E2E-тесте tests/mobile-drawers-changelog.spec.ts. Проверено отсутствие пересечений/коллизий Changelog с элементами панелей и кликабельность основных действий (Close, Generate Loop, Reverse route direction) по центру их кнопок на всех целевых viewports.
  • Ограничения: Физические устройства iPhone/Android не проверялись (валидация проводилась в эмулированных viewports Playwright).

MAP-DIALOG-001 — Фокус выходит из SaveModal

  • Статус: Open / Confirmed
  • Приоритет: P1
  • Классификация: modal keyboard accessibility
  • Среда: все viewports/themes; keyboard
  • Что уже корректно: контейнер имеет role="dialog", aria-modal="true", имя Save Track; initial focus попадает в #track-name.
  • Фактическое поведение: последовательный Tab выводит focus за пределы dialog после 14 переходов. Background toolbar/map controls снова становятся keyboard-actionable.
  • Ожидание: пока modal открыт, Tab/Shift+Tab циклически перемещаются только по его tabbable elements; фон недоступен assistive/keyboard interaction.
  • Вероятный источник: SaveModal.tsx не реализует containment/inert и не использует общий Dialog primitive.
  • Минимальное направление fix: общий focus-management primitive предпочтительнее локального keydown hack: containment, inert/aria-hidden strategy, initial focus, close, restore focus.
  • Regression guard: прямой Tab и обратный Shift+Tab cycle; assertion, что каждый activeElement находится внутри dialog; отдельно pointer test фона.
  • Exit criteria: focus не выходит, Escape закрывает только верхний dialog, после закрытия focus возвращается на Save opener.

MAP-DIALOG-002 — Один Escape закрывает dialog и нижний panel

  • Статус: Open / Confirmed
  • Приоритет: P1
  • Классификация: layer management + predictable keyboard recovery
  • Среда: Route Builder открыт, поверх него SaveModal; keyboard
  • Фактическое поведение: после одного Escape SaveModal невидим и Route Builder также закрыт. Общий document handler в MapPage.tsx сбрасывает несколько open-state одновременно; ниже также остаётся дублирующий reset code.
  • Ожидание: Escape обрабатывается только topmost dismissible surface; нижний panel сохраняет state; focus возвращается к opener.
  • Риск: пользователь теряет контекст редактирования, хотя данные маршрута не доказано потеряны. Поэтому это P1, не P0.
  • Минимальное направление fix: единый topmost-layer contract с ранним return; dialog должен остановить дальнейшее закрытие; затем убрать недостижимый/дублирующий reset.
  • Regression guard: открыть Route Builder → Save → Escape; dialog закрыт, builder открыт, focus на Save. Второй Escape может закрыть builder согласно его contract.
  • Exit criteria: одна клавиша закрывает ровно один слой во всех комбинациях Save/Export/route/loop/poi, без двойной обработки React/document handlers.

P2 — значимые follow-ups

MAP-A11Y-001 — Gradient нарушает label-in-name

  • Статус: Open / Confirmed
  • Приоритет: P2
  • Стандарт: WCAG 2.5.3 Label in Name
  • Evidence: visible text — Gradient; accessible name — Color route by slope percentage. Слово Gradient в accessible name отсутствует.
  • Fix contract: имя должно включать visible label, например Gradient — color route by slope percentage, либо visible text становится самим именем, а дополнительное объяснение уходит в description.
  • Regression guard: accessible name regex содержит Gradient; pressed-state остаётся корректным.

MAP-A11Y-002 — Недостаточный контраст Logout

  • Статус: Open / Confirmed
  • Приоритет: P2
  • Evidence: foreground #939ab7, active background #585b70, ratio 2.40:1; для normal text требуется 4.5:1.
  • Scope caution: требуется проверить default/hover/focus/disabled и icon/non-text variant, а не заменить один цвет вслепую.
  • Fix contract: semantic token или state-specific color с подтверждённым contrast во всех темах; визуально не путать destructive Logout с disabled action.
  • Regression guard: programmatic contrast probe для dark/light и relevant states.

MAP-A11Y-003 — Touch-target policy применяется непоследовательно

  • Статус: Open / Confirmed with normative split
  • Приоритет: P2
  • Normative baseline: WCAG 2.2 AA SC 2.5.8 — 24×24 или spacing exception. 32×32 само по себе не failure. Product coarse target — 44×44; WCAG 2.5.5 — AAA.
  • Measured coarse sizes: Search/Loop 44×44; MapLibre Zoom 34×34; Library/Garage/Changelog 32×32; Logout 24×24; top segments около 26px высотой; swatch 16×16.
  • Подтверждённый AA risk: соседние 16px swatches с 4px gap дают около 20px между центрами, поэтому spacing exception для 24px не выполняется очевидным образом.
  • Product ergonomics debt: MapLibre/top-right/logout/top segments не достигают согласованной 44px coarse target.
  • Fix contract: сначала закрыть swatches как нормативный минимум; затем унифицировать coarse hit areas без обязательного увеличения видимой иконки.
  • Regression guard: измерять hit box, не SVG; отдельно fine и coarse contexts.

MAP-A11Y-004 — Primary app tools поздно появляются в tab order

  • Статус: Open / Confirmed heuristic
  • Приоритет: P2
  • Evidence: первые focusable items: Map → Zoom In → Zoom Out → compass → attribution toggle → MapLibre link → Routing profile → Surface → Gradient → Color.
  • Impact: пользователь проходит map/vendor controls до основных route-planning tools при каждом входе в экран.
  • Не заявляется: keyboard trap или однозначное WCAG failure не обнаружены.
  • Fix contract: определить осознанный landmark/focus order и skip strategy; не использовать положительные tabindex как быстрый fix.
  • Regression guard: snapshot первых focus stops и доступный способ быстро перейти к primary tools/map.

MAP-VIEWPORT-001 — 100vh и safe-area contract

  • Статус: Partial / emulator verified
  • Приоритет: P2
  • Затронутые файлы: apps/web/index.html, apps/web/src/pages/map/MapPage.module.css, styles для Changelog, BottomBar, WaypointSearchBar, LoopPanel, PoiPanel, FloatingToolbar
  • Решение:
    1. В index.html добавлено viewport-fit=cover для корректного отображения safe areas.
    2. В .pageContainer определены общие CSS-переменные для отступов safe area: --safe-top, --safe-right, --safe-bottom, --safe-left на базе env(safe-area-inset-*, 0px).
    3. Эти переменные применены к Changelog, BottomBar, мобильным drawer-панелям и .topRightControls (с учетом landscape notch).
    4. Высота .pageContainer переведена на height: 100vh; height: 100dvh; для предотвращения обрезания интерфейса.
  • Verification: Успешно протестировано в Playwright на различных эмулированных viewport-ах (включая landscape notch и изменение высоты при открытой виртуальной клавиатуре).
  • Ограничения: Физические устройства iPhone/Android не проверялись (для полного закрытия issue требуется ручной Safari/iOS или Android real-device check).

MAP-MOTION-001 — Perpetual/entry animations не имеют полного reduced-motion path

  • Статус: Open / Confirmed static
  • Приоритет: P2
  • Evidence: pulseSave, HUD/dropdown/overlay animations и другие transitions определены вне единого prefers-reduced-motion contract; один существующий guard не покрывает приложение.
  • Fix contract: отключать perpetual animation и существенно сокращать non-essential transitions при reduce; сохранять status communication без движения.
  • Ограничение: глобальное правило * { transition-duration: 0.01ms !important } может сломать functional transitions и должно оцениваться осторожно.
  • Regression guard: emulation reducedMotion: reduce; computed animation name/duration/count для критических controls.

MAP-STACK-001 — Межкомпонентная z-index scale используется непоследовательно

  • Статус: Open / Static confirmed
  • Приоритет: P2
  • Evidence: ExportModal использует --z-toast; ProjectStageHUD1000; toolbar dropdown — 1000; MapPopups950/1100. Текущие tokens: map/UI/dropdown/modal/tooltip/toast scale с --z-toast: 9999.
  • Impact: возможны скрытые toast, modal, hint и drawer collisions; для каждого отдельного runtime conflict evidence пока нет.
  • Fix contract: сначала составить ownership map overlay layers, затем перенести только cross-component layers на semantic tokens. Internal z-index: 1/2/10 не запрещать автоматически.
  • Regression guard: pairwise overlay tests: modal+toast, drawer+popup, dropdown+toolbar, HUD+modal.

MAP-THEME-001 — Theme colors главной карты обходят semantic tokens

  • Статус: Open / Confirmed
  • Приоритет: P2
  • Evidence: top-right/Changelog buttons в MapPage.tsx содержат raw #313244, #45475a, #cdd6f4, hover/accent literals. Raw scanner по всему apps/web/src дал 455 совпадений с известными token values вне token source и 226 неизвестных literals.
  • Важно: scanner — triage, не число дефектов. Data ramps, map style colors, brand colors, SVG assets и комментарии требуют allowlist/semantic review.
  • Fix contract: начать с MapPage, где theme drift доказан; затем component-by-component. Не выполнять механический repo-wide replace.
  • Regression guard: dark/light computed styles и screenshot assertion конкретных components; lint/allowlist обсуждается после cleanup.

MAP-TEST-001 — Зелёные suites не ловят подтверждённые regressions

  • Статус: Open / Confirmed
  • Приоритет: P2
  • Evidence: map-layout, responsive-map-controls, toolbar-fsm прошли 3/3, несмотря на MAP-LAYOUT-001/002. Screenshot suites также прошли, но только записывают PNG и не сравнивают их с baseline.
  • Impact: CI может оставаться зелёным при недоступных controls.
  • Fix contract: geometry/hit-test assertions для cross-surface collisions, modal focus assertions, затем ограниченные toHaveScreenshot baselines для стабильных fixture states.
  • Regression guard: сам новый тест должен быть красным на baseline и зелёным только после соответствующего fix.

P3 — triage/code hygiene пакет

MAP-HYGIENE-001 — Низкоприоритетные cleanup и неподтверждённые риски

  • Статус: Open / Mixed
  • Приоритет: P3
  • Состав: дублирующий код после Escape branch; длинный development comment в trackModeOutline; BottomBar bottom: 36px; 767/768 и content-specific breakpoints; local raw radius/z-index; overlay rgba; slope legend 24×24; route hint/reset targets, для которых ещё нет полного measurement.
  • Правило: каждый пункт сначала либо связывается с воспроизводимым дефектом, либо остаётся cleanup. Не превращать пакет в массовую token rewrite.
  • Exit criteria: удалён dead code; для breakpoints/radii/internal stacking записана convention; неподтверждённые UX risks измерены и либо повышены в отдельные issues, либо закрыты как intentional.

Отдельно опровергнутые находки

Следующие пункты не должны появляться как open issues без нового evidence:

  • «SaveModal не имеет dialog semantics»;
  • «Route color fieldset не имеет label/legend»;
  • «Changelog button не имеет accessible name»;
  • «все 32×32 targets нарушают WCAG 2.2 AA»;
  • «все hardcoded border-radius: 50% нужно заменить token»;
  • «visual regression suite уже сравнивает screenshots»;
  • «focus trap в modal уже работает»;
  • «весь contrast audit PASS».