[tradein][v2][a11y] Жёсткий аудит доступности — клавиатура/SR end-to-end + aria-modal оверлеев (эпик #2262) #2264

Closed
opened 2026-07-03 06:29:38 +00:00 by lekss361 · 3 comments
Owner

Под-issue эпика #2262. Дименсия: a11y.

Задача

Полный проход /trade-in/v2 только с клавиатуры + скринридер (NVDA/VoiceOver), от ввода адреса до результата и оверлеев. Дефекты чинить, не рационализировать.

Известные точки риска

  • #2193 tradeoff: фикс сделал верхний nav кликабельным мышью над оверлеем (inert снят с content-wrap, оставлен на main/footer), но SectionOverlay остаётся aria-modal → есть паритет-разрыв mouse-vs-keyboard/SR (SR может не увидеть nav как escape из модалки). Проверить: реально ли SR-юзер может перейти на другую вкладку из открытого оверлея; focus-trap; Esc; возврат фокуса на триггер.
  • Round-2 (#2081) незакрытый: системный микро-контраст (kicker'ы/подписи --muted на светлом — возможно < 4.5:1). Прогнать axe + ручной contrast-check по всем .section-kicker, .card-sub, мелким подписям.
  • Div-таблицы аналогов/сделок (SourcesView, HistoryView, StreetDealsCard) — role=table/rowheader? читаемы SR?
  • LocationDrawer / автокомплит адреса — combobox-паттерн, aria-activedescendant, анонс результатов.
  • Форма ParamsPanel — лейблы, ошибки, aria-describedby.

Инструменты

mcp__a11y__audit_webpage (axe, высший ROI на таблицах/формах) + ручной keyboard/SR проход через mcp__playwright__*. Прод-preview /trade-in/ui-preview/estimate axe-абелен headless без auth.

DoD

Список дефектов severity-ранжирован; blocker/serious зафиксированы PR'ами (frontend-engineer); aria-modal-tradeoff #2193 либо закрыт, либо явно задокументирован как приемлемый с обоснованием.

Под-issue эпика #2262. **Дименсия: a11y.** ## Задача Полный проход `/trade-in/v2` **только с клавиатуры** + скринридер (NVDA/VoiceOver), от ввода адреса до результата и оверлеев. Дефекты чинить, не рационализировать. ## Известные точки риска - **#2193 tradeoff:** фикс сделал верхний nav кликабельным мышью над оверлеем (`inert` снят с content-wrap, оставлен на main/footer), но `SectionOverlay` остаётся `aria-modal` → есть паритет-разрыв mouse-vs-keyboard/SR (SR может не увидеть nav как escape из модалки). Проверить: реально ли SR-юзер может перейти на другую вкладку из открытого оверлея; focus-trap; Esc; возврат фокуса на триггер. - **Round-2 (#2081) незакрытый:** системный микро-контраст (kicker'ы/подписи `--muted` на светлом — возможно < 4.5:1). Прогнать axe + ручной contrast-check по всем `.section-kicker`, `.card-sub`, мелким подписям. - **Div-таблицы** аналогов/сделок (SourcesView, HistoryView, StreetDealsCard) — role=table/rowheader? читаемы SR? - **LocationDrawer / автокомплит адреса** — combobox-паттерн, aria-activedescendant, анонс результатов. - Форма ParamsPanel — лейблы, ошибки, `aria-describedby`. ## Инструменты `mcp__a11y__audit_webpage` (axe, высший ROI на таблицах/формах) + ручной keyboard/SR проход через `mcp__playwright__*`. Прод-preview `/trade-in/ui-preview/estimate` axe-абелен headless без auth. ## DoD Список дефектов severity-ранжирован; blocker/serious зафиксированы PR'ами (frontend-engineer); aria-modal-tradeoff #2193 либо закрыт, либо явно задокументирован как приемлемый с обоснованием.
lekss361 added the
scope/frontend
tradein
ux
labels 2026-07-03 06:31:14 +00:00
Author
Owner

Аудит выполнен (code-review ревизии 35a0e291 + живой клавиатурный проход на проде, 2026-07-03)

Вердикт по aria-modal-tradeoff #2193: ЧИНИТЬ, не документировать. Живой прогон подтвердил «полумодальность» SectionOverlay: мышью верхний nav кликабелен (backdrop top:80, elementFromPoint попадает в кнопки), Tab'ом — недостижим (строгий документ-уровневый focus-trap), для SR фон вообще не скрыт (live DOM: nav/main/footer без inert/aria-hidden, только негарантированный aria-modal). Направление фикса: честный немодальный fullscreen-view — снять aria-modal, ослабить Tab-trap до «оверлей + живой nav», гарантировать inert на main/footer (в коде он есть — page.tsx:779-782 — но в живом DOM атрибуты отсутствуют, расхождение разбирается).

Findings (объединённые):

  • SERIOUS: полумодальность SectionOverlay (выше); микро-контраст — 6 токенов (muted2/3/4, hint, disabledValue/Label в tokens.ts) дают 4.2–4.5:1 над blueprint-сеткой (worst-case #dce6f1) при тексте 9–11px — остаток round-2 #2081, ~95% тогда закрыли, это хвост.
  • MODERATE: ни одного h1–h6 во всём v2 (axe preview: page-has-heading-one + landmark-one-main); «КОЭФ. ЛОКАЦИИ» — клик-only div без клавиатурного доступа; accent #2e8bff как мелкий текст (2.87:1); фокус после submit падает на body; drawer «МЕТОДИКА» на первом маунте без aria-hidden (после reload — корректно); слабый фокус-ринг текстовых инпутов (box-shadow 18% alpha).
  • MINOR: нет live-анонса «N подсказок» в комбобоксе; нет aria-required на обязательных полях; декоративные SVG без aria-hidden; заголовок оверлея не отражает точку входа («Источники → Подробнее» открывает «АНАЛОГИЧНЫЕ ОБЪЯВЛЕНИЯ»).

Что уже хорошо (round-2 фиксы подтверждены живьём): AddressInput — образцовый ARIA-combobox (aria-activedescendant живое); LocationDrawer — полноценная модалка с focus-trap/Esc/возвратом фокуса; div-таблицы с role=table/columnheader и aria-label; Esc + возврат фокуса на триггер работают везде; golden-path от адреса до результата полностью проходим клавиатурой.

Фиксы (все 12 пунктов) — воркер уже пишет, PR следующим комментом.

## Аудит выполнен (code-review ревизии 35a0e291 + живой клавиатурный проход на проде, 2026-07-03) **Вердикт по aria-modal-tradeoff #2193: ЧИНИТЬ, не документировать.** Живой прогон подтвердил «полумодальность» SectionOverlay: мышью верхний nav кликабелен (backdrop top:80, elementFromPoint попадает в кнопки), Tab'ом — недостижим (строгий документ-уровневый focus-trap), для SR фон вообще не скрыт (live DOM: nav/main/footer без inert/aria-hidden, только негарантированный aria-modal). Направление фикса: честный **немодальный** fullscreen-view — снять `aria-modal`, ослабить Tab-trap до «оверлей + живой nav», гарантировать inert на main/footer (в коде он есть — `page.tsx:779-782` — но в живом DOM атрибуты отсутствуют, расхождение разбирается). **Findings (объединённые):** - SERIOUS: полумодальность SectionOverlay (выше); микро-контраст — 6 токенов (`muted2/3/4`, `hint`, `disabledValue/Label` в `tokens.ts`) дают 4.2–4.5:1 над blueprint-сеткой (worst-case #dce6f1) при тексте 9–11px — остаток round-2 #2081, ~95% тогда закрыли, это хвост. - MODERATE: ни одного h1–h6 во всём v2 (axe preview: page-has-heading-one + landmark-one-main); «КОЭФ. ЛОКАЦИИ» — клик-only div без клавиатурного доступа; accent #2e8bff как мелкий текст (2.87:1); фокус после submit падает на body; drawer «МЕТОДИКА» на первом маунте без aria-hidden (после reload — корректно); слабый фокус-ринг текстовых инпутов (box-shadow 18% alpha). - MINOR: нет live-анонса «N подсказок» в комбобоксе; нет aria-required на обязательных полях; декоративные SVG без aria-hidden; заголовок оверлея не отражает точку входа («Источники → Подробнее» открывает «АНАЛОГИЧНЫЕ ОБЪЯВЛЕНИЯ»). **Что уже хорошо (round-2 фиксы подтверждены живьём):** AddressInput — образцовый ARIA-combobox (aria-activedescendant живое); LocationDrawer — полноценная модалка с focus-trap/Esc/возвратом фокуса; div-таблицы с role=table/columnheader и aria-label; Esc + возврат фокуса на триггер работают везде; golden-path от адреса до результата полностью проходим клавиатурой. Фиксы (все 12 пунктов) — воркер уже пишет, PR следующим комментом.
Author
Owner

Фиксы влиты: PR #2279 (merged 2026-07-03)

Все 12 пунктов аудита закрыты (2 оказались уже исправленными на HEAD — drawer aria-hidden и preview main/h1):

  • aria-modal-tradeoff #2193 → ЗАКРЫТ фиксом: SectionOverlay теперь честный немодальный fullscreen-view — aria-modal снят, focus-trap переписан (Tab-цикл = живые табы верхнего nav + контролы оверлея, inert-поддеревья исключены), Esc/возврат фокуса сохранены. Паритет mouse/keyboard/SR восстановлен.
  • Контраст (остаток #2081): 6 токенов затемнены до ≥4.6:1 vs worst-case #dce6f1 (muted2 #5c6672, muted3 #5d6671, muted4 #5f666e, hint #5b6672, disabledValue #60666c, disabledLabel #5f666e).
  • Headings (sr-only h1 + h2), «КОЭФ. ЛОКАЦИИ» кнопкой, фокус на результат после submit, solid focus-ring, aria-live «N подсказок», aria-required, aria-hidden на 14 SVG.

Ревью: APPROVE (focus-trap edge-cases проверены статически). CI зелёный, tsc/next build exit 0.

Остаток (не блокер, задокументирован): accent #2e8bff как крупный текст (headline-цена 40px ≈2.87:1, номера секций «02»/«03») — ниже AA-large; визуально-тяжёлая правка, решать вместе с визуальным треком #2266.

Post-deploy QA (rule #7): клавиатурный smoke на проде после деплоя — Tab nav↔оверлей, Esc+возврат фокуса, анонс подсказок.

## Фиксы влиты: PR #2279 (merged 2026-07-03) Все 12 пунктов аудита закрыты (2 оказались уже исправленными на HEAD — drawer aria-hidden и preview main/h1): - **aria-modal-tradeoff #2193 → ЗАКРЫТ фиксом**: SectionOverlay теперь честный немодальный fullscreen-view — `aria-modal` снят, focus-trap переписан (Tab-цикл = живые табы верхнего nav + контролы оверлея, inert-поддеревья исключены), Esc/возврат фокуса сохранены. Паритет mouse/keyboard/SR восстановлен. - **Контраст (остаток #2081)**: 6 токенов затемнены до ≥4.6:1 vs worst-case #dce6f1 (muted2 #5c6672, muted3 #5d6671, muted4 #5f666e, hint #5b6672, disabledValue #60666c, disabledLabel #5f666e). - Headings (sr-only h1 + h2), «КОЭФ. ЛОКАЦИИ» кнопкой, фокус на результат после submit, solid focus-ring, aria-live «N подсказок», aria-required, aria-hidden на 14 SVG. Ревью: ✅ APPROVE (focus-trap edge-cases проверены статически). CI зелёный, tsc/next build exit 0. **Остаток (не блокер, задокументирован):** accent #2e8bff как крупный текст (headline-цена 40px ≈2.87:1, номера секций «02»/«03») — ниже AA-large; визуально-тяжёлая правка, решать вместе с визуальным треком #2266. **Post-deploy QA (rule #7):** клавиатурный smoke на проде после деплоя — Tab nav↔оверлей, Esc+возврат фокуса, анонс подсказок.
Author
Owner

QA-smoke на проде (2026-07-03) — PASS

Живой Playwright-прогон под pilottest на https://gendsgn.ru/trade-in/v2 после деплоя PR #2279:

  • aria-modal отсутствует на SectionOverlay dialog (role=dialog, aria-modal — нет атрибута вообще). Второй, реально модальный drawer («МЕТОДИКА») корректно сохраняет aria-modal=true — не задет фиксом.
  • Tab/Shift+Tab из открытого оверлея достигает верхнего nav — фокус не заперт, закрывает старый tradeoff #2193.
  • Esc закрывает оверлей + фокус возвращается на кнопку-триггер — подтверждено.
  • sr-only h1 на странице результата + h2 внутри оверлея — оба реально в DOM.
  • aria-required на полях адреса/площади формы ввода — подтверждено.
  • Видимый focus-ring при Tab-навигации, :focus-visible не подавлен.
  • GlitchTip: 0 новых frontend/aria-ошибок, console errors: 0, все network-запросы 200.

Один нюанс, который я изначально сформулировал в QA-задаче неточно: main/footer сохраняют inert при открытом оверлее (не теряют его) — это и есть корректное поведение (фон визуально скрыт full-screen оверлеем → скрыт и от AT), при этом верхний nav — DOM-сиблинг main, сам не inert, поэтому остаётся достижим клавиатурой (что независимо подтверждено пунктом выше). Никакого регресса нет, ложная тревога от формулировки моей QA-задачи, не от кода.

Вердикт: PASS. Закрываю issue и отмечаю чекбокс a11y в эпике #2262.

## QA-smoke на проде (2026-07-03) — PASS Живой Playwright-прогон под pilottest на https://gendsgn.ru/trade-in/v2 после деплоя PR #2279: - **aria-modal отсутствует** на SectionOverlay dialog (`role=dialog`, `aria-modal` — нет атрибута вообще). Второй, реально модальный drawer («МЕТОДИКА») корректно сохраняет `aria-modal=true` — не задет фиксом. - **Tab/Shift+Tab из открытого оверлея достигает верхнего nav** — фокус не заперт, закрывает старый tradeoff #2193. - **Esc закрывает оверлей + фокус возвращается на кнопку-триггер** — подтверждено. - **sr-only h1 на странице результата + h2 внутри оверлея** — оба реально в DOM. - **aria-required** на полях адреса/площади формы ввода — подтверждено. - **Видимый focus-ring** при Tab-навигации, `:focus-visible` не подавлен. - GlitchTip: 0 новых frontend/aria-ошибок, console errors: 0, все network-запросы 200. Один нюанс, который я изначально сформулировал в QA-задаче неточно: main/footer **сохраняют** `inert` при открытом оверлее (не теряют его) — это и есть корректное поведение (фон визуально скрыт full-screen оверлеем → скрыт и от AT), при этом верхний nav — DOM-сиблинг main, сам не inert, поэтому остаётся достижим клавиатурой (что независимо подтверждено пунктом выше). Никакого регресса нет, ложная тревога от формулировки моей QA-задачи, не от кода. **Вердикт: PASS.** Закрываю issue и отмечаю чекбокс a11y в эпике #2262.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2264
No description provided.