Appearance
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
Пользователь читает документы и подтверждает:
- шкалу
P0–P3; - что
P1исправляются первыми; - что первый 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.cssapps/web/src/widgets/floating-toolbar/FloatingToolbar.tsxapps/web/src/widgets/map-canvas/MapCanvas.tsxили его control stylingapps/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и landscape844×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.tsxapps/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:
- initial focus в SaveModal;
- полный
Tabcycle остаётся внутри; - обратный
Shift+Tabcycle остаётся внутри; - background не keyboard/pointer actionable;
- Escape закрывает только SaveModal;
- Route Builder остаётся открыт;
- focus восстанавливается на Save opener;
- второй 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.tsxapps/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-labelChangelog как «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/internal | canvas, route decorations | map-local |
| App chrome | toolbar, bottom bar | UI/chrome |
| Dropdown/popup | color menu, context menu | dropdown/popover |
| Drawer/HUD | mobile drawer, stage HUD | overlay |
| Modal | Save/Export/confirm | modal |
| Tooltip | hints | tooltip |
| Toast | global feedback | toast, всегда над modal |
Fix order
ExportModalс toast layer;ProjectStageHUD;- cross-widget
MapPopupslayers; - FloatingToolbar dropdown;
- только затем 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.
Последовательность
MapPage.tsxtop-right/Changelog colors — доказанный theme drift;- компоненты главной карты с повторяющимися known-token literals;
SurfaceBreakdownпосле решения, являются ли цвета semantic status или stable data ramp;- радиусы/breakpoints — только с утверждённой convention;
- 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.