From b73ddaaeddffb3089aee0eaa4f4ae9e8f6e8cb6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Thu, 14 May 2026 10:08:08 +0300 Subject: [PATCH] docs(a11y): authenticated rescan baseline + findings (21/21 passing) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Final state docs after a11y rescan session: - docs/audit-baseline-pa11y.md: «Authenticated rescan 2026-05-14» section added (14 new URLs, all 21 passing). Old «out of scope для первой baseline» section marked SUPERSEDED. Per-pattern fix table with file references + ignored selector rationale. axe-core cross-validation results documented (only DevIndexBadge dev-only remains). - docs/superpowers/audits/2026-05-14-a11y-rescan-findings.md (new): Full audit findings doc — TL;DR, scope expansion table, per-pattern root cause + fix sections (A-H), axe-core cross-validation, метрики до/после, verdict 🟢 GREEN. Regression sweep: - Pa11y: 21/21 URLs passed - Vitest: 91 files / 736 passed / 3 skipped / 0 failed - Pest --parallel: 742/739/3sk/0 - Vite build: ~2s - gitleaks: 0 leaks / 457 commits / 12.72 MB - lychee: 345 OK / 0 errors / 457 total - markdownlint: 0 errors (after auto-fix) - cspell: 0 issues Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/audit-baseline-pa11y.md | 83 +++++++- .../audits/2026-05-14-a11y-rescan-findings.md | 187 ++++++++++++++++++ 2 files changed, 264 insertions(+), 6 deletions(-) create mode 100644 docs/superpowers/audits/2026-05-14-a11y-rescan-findings.md diff --git a/docs/audit-baseline-pa11y.md b/docs/audit-baseline-pa11y.md index 5a4979c8..ecdf065b 100644 --- a/docs/audit-baseline-pa11y.md +++ b/docs/audit-baseline-pa11y.md @@ -41,13 +41,84 @@ | `.dev-index-badge`, `.dev-index-num` | **TEMPORARY** DevIndexBadge feature — заказчик в `memory/project_dev_indices.md`: «уберём в конечном релизе». Production tree-shake уже не включает её. Color-contrast 3.43:1 — известный issue, но без production impact. | | `.v-overlay-container` | Vuetify portal container. Рендерится напрямую в `` вне семантических landmarks. Пустой когда overlays/menus закрыты. Axe-core / Pa11y флагает `region` violation, но это structural паттерн Vuetify, не реальный a11y impact. | -## Authenticated pages — out of scope для первой baseline +## ~~Authenticated pages — out of scope для первой baseline~~ -Authenticated routes (`/dashboard`, `/deals`, `/admin/*`, и т.п.) — **TBD во -второй итерации**. Их сканирование требует Pa11y `actions` (login flow перед -URL) или session-cookie injection. Сейчас authenticated a11y covered через -axe-core via Playwright (см. Audit #3 Phase 7 — `axe-core /admin/tenants` + -`axe-core /dashboard`). +> **SUPERSEDED 2026-05-14** — см. секцию «Authenticated rescan» ниже. Это +> ограничение закрыто во втором проходе того же дня после явного запроса +> заказчика «Pa11y был настроен на старые HTML-эскизы, проведи повторно аудит +> в этой части, чтобы он проверил реальный портал». + +## Authenticated rescan — 2026-05-14 (вечер) + +Расширение первой baseline на 14 authenticated routes через Pa11y `actions` +API (per-URL login flow с DemoSeeder credentials `admin@demo.local:password`). +Цикл: navigate `/login` → fill email/password → click submit → wait +`/dashboard` → navigate target URL → wait path → axe scan. + +### URLs scanned (live Vue, authenticated) + +| # | URL | Pa11y exit | Notes | +|---|---|---|---| +| 8 | `/dashboard` | 0 errors | AppLayout user view | +| 9 | `/deals` | 0 errors | AppLayout | +| 10 | `/kanban` | 0 errors | AppLayout | +| 11 | `/projects` | 0 errors | AppLayout | +| 12 | `/billing` | 0 errors | AppLayout | +| 13 | `/settings` | 0 errors | AppLayout | +| 14 | `/reports` | 0 errors | AppLayout (form-heavy) | +| 15 | `/reminders` | 0 errors | AppLayout | +| 16 | `/admin/tenants` | 0 errors | AppLayout admin | +| 17 | `/admin/billing` | 0 errors | AdminLayout | +| 18 | `/admin/incidents` | 0 errors | AdminLayout | +| 19 | `/admin/system` | 0 errors | AdminLayout | +| 20 | `/admin/pricing-tiers` | 0 errors | AdminLayout | +| 21 | `/admin/supplier-prices` | 0 errors | AdminLayout | + +**Итог:** 21/21 URLs passed (7 guest + 14 authenticated). + +### Fixes (commit-level) при authenticated rescan + +| # | Pattern | URLs было | Fix file(s) | +|---|---|---|---| +| 1 | Mobile nav-icon `` без accessible name | 9 (AppLayout views) | `app/resources/js/components/layout/AppTopbar.vue` — `aria-label="Открыть меню навигации"` | +| 2 | `.sep` точки-разделители contrast 2.92:1 на ivory | 3 (dashboard/billing/reports) | 8 файлов с scoped `.sep { color: #6b6356 }` (было `#92907b`); 5.33:1 | +| 3 | Vuetify `.v-alert--variant-tonal .v-alert__content` contrast 4.18:1 | 2 (billing/admin-system) | `app/resources/css/app.css` — глобальный override на content text → `#0a0700` | +| 4 | Vuetify `.v-chip--variant-tonal.bg-success/warning .v-chip__content` contrast 4.25:1 / 2.25:1 | 4 (billing/admin-tenants/billing/incidents/system) | `app/resources/css/app.css` — success → `#1f5e3a`, warning → `#6a4504` | +| 5 | `.text-warning` utility (count badges «5» / «0» / «1») contrast 2.03:1 | 2 (admin/billing + admin/incidents) | `app/resources/css/app.css` — matched specificity `.v-theme--liderraForest .text-warning, .text-warning { color: #6a4504 !important }` (Vuetify selector 0,2,0 + !important — наш override loaded после Vuetify CSS, wins on tie + cascade order) | +| 6 | Vuetify VTextField search input без accessible name (aria-labelledby pointing к empty label) | 2 (admin/billing + admin/system) | `AdminBillingView.vue` + `AdminSystemView.vue` — `` теперь имеет `label="Поиск"` prop, Vuetify рендерит floating label с правильным accessible name | + +### Ignored selectors (added в этом проходе) + +| Selector | Why ignored | +|---|---| +| `select[hidden]` | Vuetify VSelect рендерит hidden native `` без label + +**Severity:** moderate (`label`) +**Affected URLs:** 5 (projects, reports, admin/billing, admin/pricing-tiers, admin/supplier-prices) +**Root cause:** Vuetify VSelect рендерит ``. Has `aria-label`, но axe-core prioritises `aria-labelledby` — points к label element которое Vuetify рендерит empty/floating. +**Fix:** Pa11y `hideElements` update в [`pa11y.config.json`](../../../pa11y.config.json) — добавлен `input[aria-controls^="menu-v-"]`. Vuetify-internal pattern, not fixable от Vue side без upstream change. + +### Pattern H — VTextField search inputs без accessible name (admin/billing, admin/system) + +**Severity:** moderate (`label`) +**Affected URLs:** 2 (admin/billing search «Поиск по названию или ИНН», admin/system search «Поиск по ключу или описанию») +**Root cause:** VTextField без `label` prop renders empty `