Skip to content

03. Поэтапный план исправления

Главный принцип

Работа идёт маленькими reviewable этапами. Один этап — один смысловой риск, один набор красных regression tests, минимальный fix и отдельный отчёт. Переход к следующему этапу происходит только после просмотра пользователем.

Definition of Done для каждого этапа

Каждый этап считается законченным только если выполнены все пункты:

  • issue scope и non-goals зафиксированы до редактирования;
  • новый regression test падает на старом поведении по правильной причине;
  • product fix минимален и не включает соседний cleanup;
  • targeted suite зелёный;
  • проверены соседние viewport/state/theme combinations;
  • сохранены before/after geometry и screenshots;
  • в реестре обновлены status, commit и evidence;
  • в матрице retest обновлены только реально прогнанные строки;
  • пользователю показаны diff, результаты тестов и оставшиеся риски;
  • после этого работа останавливается для review.

Phase 0 — сохранить baseline

Статус: выполнен этим комплектом документов; product code не менялся.

Scope

  • сверить три исходных отчёта;
  • повторить ключевые geometry/a11y probes;
  • сохранить commit, dirty-tree context, команды и screenshots;
  • нормализовать severity и WCAG формулировки;
  • создать канонический issue ledger и этапы.

Deliverables

  • документы README + 01–05;
  • docs/public/audits/main-map-2026-07/evidence/geometry-baseline.json;
  • подтверждение, что baseline tests запускаются через system Chrome.

Review checkpoint 0

Пользователь читает документы и подтверждает:

  1. шкалу P0–P3;
  2. что P1 исправляются первыми;
  3. что первый code change ограничен mobile top zone.

Phase 1 — mobile top zone и MapLibre controls

Закрывает: MAP-LAYOUT-001Риск: высокий, но локализованный; затрагивает самую плотную область интерфейса.

Сначала RED

Добавить целевой Playwright spec, который для 320, 360, 375, 390, 412 проверяет:

  • первый toolbar row полностью внутри viewport;
  • MapLibre Zoom/Compass полностью внутри viewport;
  • toolbar row и каждый MapLibre control не пересекаются;
  • hit-test центра controls возвращает control/descendant;
  • horizontal scroll второго tools row не создаёт page-level horizontal scroll;
  • focus outline не скрыт верхним слоем.

Отдельная landscape строка — 844×390.

Минимальный product fix

Варианты оцениваются по geometry, а не выбираются заранее:

  • зарезервировать top-right control safe zone;
  • перестроить/сжать первый ряд на phone;
  • перенести MapLibre controls в свободную corner/offset zone;
  • при необходимости вынести secondary Color control во второй ряд.

Нельзя одновременно переносить весь toolbar в Bottom Bar: это новый interaction design и больший regression surface.

Вероятно затронутые файлы

  • apps/web/src/widgets/floating-toolbar/FloatingToolbar.module.css
  • apps/web/src/widgets/floating-toolbar/FloatingToolbar.tsx
  • apps/web/src/widgets/map-canvas/MapCanvas.tsx или его control styling
  • apps/web/tests/responsive-map-controls.spec.ts либо новый focused spec

Обязательный retest

  • dark/light;
  • fine/coarse pointer;
  • M01 IDLE, M03 DRAW, color tray open, long/localized labels;
  • keyboard focus от MapLibre controls до toolbar;
  • desktop 1280×800 и 1440×900 как non-regression.

Non-goals

  • Changelog/drawers;
  • modal focus;
  • массовый token cleanup;
  • redesign всех map controls.

Review checkpoint 1

Показать side-by-side screenshots на 320, 390, 412, geometry table до/после и CSS/TSX diff. Не начинать Phase 2.

Phase 2 — mobile drawers, Changelog и viewport edges

Закрывает: MAP-LAYOUT-002; частично подготавливает MAP-VIEWPORT-001.

Сначала RED

  • открыть Loop, Search/Route Builder и POI drawers по очереди;
  • собрать все visible interactive descendants в нижней зоне;
  • проверить пересечение с Changelog, BottomBar, browser-safe bottom zone;
  • проверить center hit-test primary action;
  • добавить portrait 320/390/412 и landscape 844×390.

Минимальный product fix

Выбрать единый state-aware contract:

  • скрывать Changelog только при mobile drawer;
  • или сдвигать его выше реальной drawer height;
  • или размещать secondary global actions внутри согласованной mobile chrome.

После устранения collision добавить 100vh fallback + выбранную dynamic viewport единицу и safe-area offsets для реально edge-pinned containers. Эти изменения делаются в том же этапе только потому, что меняют одну нижнюю/краевую geometry system; если diff разрастается, viewport часть выделяется в Phase 2b.

Вероятно затронутые файлы

  • apps/web/src/pages/map/MapPage.tsx
  • apps/web/src/pages/map/MapPage.module.css
  • styles Search/Loop/POI drawers и BottomBar.module.css
  • focused mobile-collision test

Обязательный retest

  • Changelog доступен в IDLE без drawer;
  • primary actions не перекрыты;
  • drawer scroll и on-screen keyboard не прячут CTA;
  • notch/home indicator проверен хотя бы в Safari emulation; real device остаётся обязательным для полного закрытия safe-area issue;
  • desktop Changelog position не изменился.

Non-goals

  • изменение содержимого drawers;
  • новый navigation architecture;
  • modal stack.

Review checkpoint 2

Показать Loop/Search/POI screenshots до/после, intersection values и отдельный список того, что ещё не подтверждено без физического iPhone/Android.

Phase 3 — modal focus, layer stack и Escape

Закрывает: MAP-DIALOG-001, MAP-DIALOG-002.

Сначала RED

Добавить tests:

  1. initial focus в SaveModal;
  2. полный Tab cycle остаётся внутри;
  3. обратный Shift+Tab cycle остаётся внутри;
  4. background не keyboard/pointer actionable;
  5. Escape закрывает только SaveModal;
  6. Route Builder остаётся открыт;
  7. focus восстанавливается на Save opener;
  8. второй Escape обрабатывает следующий слой.

Повторить минимум для ещё одной modal, чтобы shared primitive не оказался Save-only hack.

Минимальный product fix

  • централизовать dialog focus management;
  • обозначить topmost overlay ownership;
  • не держать конкурирующие Escape listeners на document и внутри modal без stop contract;
  • удалить дублирующие reset statements после исправления поведения.

Если в проекте уже есть подходящий доступный Dialog primitive, переиспользовать его. Если нет — создать узкий shared primitive с tests, не переписывая сразу все modals.

Вероятно затронутые файлы

  • apps/web/src/widgets/save-modal/SaveModal.tsx
  • apps/web/src/pages/map/MapPage.tsx
  • shared dialog/focus utility при необходимости
  • новый modal-layering.spec.ts

Обязательный retest

  • Save из Route Builder и обычного route state;
  • Escape, close button, Cancel, submit;
  • focus restoration при удалении opener из DOM;
  • mobile + desktop;
  • screen-reader snapshot dialog name/description.

Non-goals

  • визуальный redesign modal;
  • одновременная миграция каждой modal в репозитории, если общий primitive ещё не проверен.

Review checkpoint 3

Показать focus trace до/после, accessibility snapshot, видео/скрин последовательности Escape и test diff.

Phase 4 — точечные a11y и ergonomics fixes

Закрывает: MAP-A11Y-001, MAP-A11Y-002, MAP-A11Y-003; принимает решение по MAP-A11Y-004.

Подэтап 4.1 — accessible names

  • исправить Gradient label-in-name;
  • сохранить pressed state и group name;
  • не добавлять лишний aria-label Changelog как «fix отсутствующего имени», потому что имя уже есть; можно отдельно выбрать более надёжную naming strategy.

Подэтап 4.2 — contrast

  • измерить Logout default/hover/focus в dark/light;
  • выбрать semantic token;
  • проверить минимум 4.5:1 для text и 3:1 для meaningful non-text boundary/focus indicator, где применимо.

Подэтап 4.3 — target sizes

  • сначала swatches: hit area/spacing до AA contract;
  • затем product coarse target для top segments, MapLibre, Library/Garage/Changelog/Logout;
  • визуальный размер icon может остаться прежним, меняется hit box/spacing.

Подэтап 4.4 — focus order decision

Сначала UX-решение: primary app toolbar или map controls должны быть раньше? Рассмотреть landmarks/skip link. Не менять порядок CSS-визуально без DOM/accessibility проверки и не использовать positive tabindex.

Tests

  • accessibility snapshot/role locators;
  • contrast helper;
  • target-size probe fine/coarse;
  • focus order snapshot;
  • keyboard-visible focus screenshots.

Review checkpoint 4

Показать по одному маленькому diff на names, contrast и targets. Focus-order изменение выполнять только после отдельного подтверждения решения.

Phase 5 — stacking architecture

Закрывает: MAP-STACK-001.

Inventory before edits

Составить таблицу владельцев cross-component layers:

Layer roleПримерыОжидаемый token
Map/internalcanvas, route decorationsmap-local
App chrometoolbar, bottom barUI/chrome
Dropdown/popupcolor menu, context menudropdown/popover
Drawer/HUDmobile drawer, stage HUDoverlay
ModalSave/Export/confirmmodal
Tooltiphintstooltip
Toastglobal feedbacktoast, всегда над modal

Fix order

  1. ExportModal с toast layer;
  2. ProjectStageHUD;
  3. cross-widget MapPopups layers;
  4. FloatingToolbar dropdown;
  5. только затем remaining raw cross-component values.

Pairwise tests

  • modal + toast;
  • dropdown + MapLibre controls;
  • drawer + popup;
  • HUD + modal;
  • context menu + waypoint/route popup.

Non-goals

  • запрещать local z-index: 1/2;
  • заменить каждый number tokenом;
  • менять geometry вместе со stacking без collision test.

Review checkpoint 5

Показать layer ownership table, token diff и pairwise screenshots. Любой новый token должен иметь один ясный semantic role.

Phase 6 — reduced motion

Закрывает: MAP-MOTION-001.

Work

  • инвентаризировать infinite и interaction-triggered animations;
  • при prefers-reduced-motion: reduce остановить pulse/loading motion или заменить статичным state;
  • сократить decorative transitions, сохранив functional visibility;
  • проверить, что dropdown/modal не остаются невидимыми из-за отменённого animation end.

Tests

  • Playwright context reducedMotion: 'reduce';
  • computed animation duration/iteration count;
  • Save loading, HUD, dropdown, modal, toast;
  • normal-motion non-regression.

Review checkpoint 6

Показать computed-style table и короткие normal/reduce captures. Не смешивать с theme cleanup.

Phase 7 — theme tokens и ограниченный design-system cleanup

Закрывает: MAP-THEME-001; часть MAP-HYGIENE-001.

Последовательность

  1. MapPage.tsx top-right/Changelog colors — доказанный theme drift;
  2. компоненты главной карты с повторяющимися known-token literals;
  3. SurfaceBreakdown после решения, являются ли цвета semantic status или stable data ramp;
  4. радиусы/breakpoints — только с утверждённой convention;
  5. scanner allowlist для brand/map/data/SVG exceptions.

Tests

  • dark/light screenshots конкретного component state;
  • contrast для text/icons/borders;
  • scanner delta с объяснёнными remaining literals;
  • отсутствие mass diff generated/map-style assets.

Non-goals

  • repo-wide search-and-replace;
  • tokenization border-radius: 50%;
  • превращение уникального data color в theme surface token.

Review checkpoint 7

Показывать изменения по компонентам, а не одним огромным diff. После каждого component cluster — отдельная остановка при заметном visual change.

Phase 8 — полный regression audit и release decision

Закрывает: MAP-TEST-001 и release gate.

Required coverage

  • Tier A states M01–M06 на обязательных viewports/themes;
  • Tier B transitions, включая dismiss/restore;
  • pairwise basemap/color/input coverage;
  • width sweep 320–1920 для geometry invariants;
  • Chromium + Firefox + WebKit либо явно задокументированное исключение;
  • real touch/safe-area check;
  • keyboard, reduced motion, 200% zoom/reflow;
  • console/page errors и fixture-controlled network.

Visual regression

Стабильные fixture states переводятся с capture-only на toHaveScreenshot/image-diff. Dynamic tiles, timestamps и animations маскируются или стабилизируются; иначе screenshot остаётся evidence, но не gate.

Финальный release gate

PASS возможен только если:

  • P1 = 0;
  • нормативные P2 закрыты или имеют явно принятое исключение;
  • mandatory ledger заполнен фактическими PASS/FAIL, без арифметических противоречий;
  • screenshots и raw reports доступны;
  • limitations перечислены, а не скрыты общим словом «PASS».

Review checkpoint 8

Показать финальный audit report, residual risk и список осознанно отложенных P3. Только после этого менять release verdict.