From 143cc458c19ace2b0c748be0a0a48c5670f844ab 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: Tue, 12 May 2026 22:09:48 +0300 Subject: [PATCH] =?UTF-8?q?fix(a11y):=20Q.DEFER.002=20sub-B=20=E2=80=94=20?= =?UTF-8?q?12=20patterns=20fixed=20across=2016=20auth=20views?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Q.DEFER.002 sub-B closure: manual Pa11y audit-pass via Playwright MCP login + axe-core CDN inject on 16 auth-required views. Found ~13 unique violation patterns, 12 fixed, 3 deferred to Q.DEFER.004. ROOT CAUSE found: AdminLayout `` resolved to Vuetify default-dark `secondary=#54b6b2` (Teal mid) instead of liderraForest `#012019` теало-нуар. Switching to direct hex preserves design intent + restores white-text contrast across all 8 admin views (~50 nodes color-contrast violations cleared). Patterns fixed: 1. AdminLayout sidebar palette (8 admin views): - color="secondary" → color="#012019" (root cause) - .brand-sub red #b94837 → #e06155 (3.41 → 5.08) - .nav-count gray #7a8c87 → #8a9c95 (4.26 → 5.34) - + role="navigation" + aria-label (aria-required-children fix: had [role=link] children — undefined для list) 2. DashboardBalance .runway-bar — role="img" (aria-prohibited-attr fix) 3. DashboardKpiRow .delta-up — #2e8b57 → #1b6e3b (4.27 → 6.25) 4. TransactionsTable .tx-amount-up — #2e8b57 → #1b6e3b (same fix) 5. RemindersList .empty-hint — #9a9690 → #6b6356 (2.98 → 5.74; +liderra-muted alignment) 6. KanbanView .kanban-board — tabindex="0" role="region" aria-label (scrollable-region-focusable fix) 7. ProjectCard: - .v-progress-linear + :aria-label="Прогресс дневной нормы: N%" - icon menu :aria-label="Меню действий проекта «...»" - bulk-select .card-check input :aria-label="Выбрать проект «...»" 8. useStatusPill in_progress #3F7C95 → #2A5A6E (4.07 → 6.11); useStatusPill.spec.ts sync 9. ProjectsView toolbar select-all input aria-label 10. AdminTenants impersonate v-btn aria-label 11. Global app.css: `.v-messages, .v-field-label { --v-medium-emphasis-opacity: 0.7; }` Vuetify default ~0.52 → rendered #7a7a7a/#767471 fails 4.20-4.29:1; 0.7 → rendered ≈#595959 → 7.9:1+ passes WCAG AA. Re-verified post-fix via axe-core on all affected views: all clean except DEV-only `.dev-index-num` chip (tree-shaked в prod, not a real violation). Vitest verified post-fix: 79 files / 614 passed / 3 skipped / 0 failed (baseline preserved). 3 patterns deferred to Q.DEFER.004: - DealsTable VDataTable show-select bulk-checkboxes (6 nodes) — Vuetify slot rewrite needed - AdminSupplierPrices 9 form inputs — v-text-field/v-switch label props - Vuetify v-tooltip eager-mount aria-tooltip-name — library-level cosmetic Co-Authored-By: Claude Opus 4.7 (1M context) --- app/resources/css/app.css | 11 +++++ .../components/admin/tenants/TenantsTable.vue | 1 + .../components/billing/TransactionsTable.vue | 2 +- .../components/dashboard/DashboardBalance.vue | 2 +- .../components/dashboard/DashboardKpiRow.vue | 2 +- .../js/components/projects/ProjectCard.vue | 23 ++++++++-- .../js/components/reminders/RemindersList.vue | 2 +- app/resources/js/composables/useStatusPill.ts | 2 +- app/resources/js/layouts/AdminLayout.vue | 8 ++-- app/resources/js/views/KanbanView.vue | 2 +- app/resources/js/views/ProjectsView.vue | 1 + app/tests/Frontend/useStatusPill.spec.ts | 2 +- .../2026-05-12-portal-full-audit-blocked.md | 46 ++++++++++++++++++- 13 files changed, 89 insertions(+), 15 deletions(-) diff --git a/app/resources/css/app.css b/app/resources/css/app.css index c2517873..22fea661 100644 --- a/app/resources/css/app.css +++ b/app/resources/css/app.css @@ -23,3 +23,14 @@ body { font-family: 'JetBrains Mono', ui-monospace, monospace; font-feature-settings: 'tnum' 1; } + +/* + * A11y override: Vuetify .v-messages helper-text + .v-field-label opacity + * (~0.52 default) рендерится ≈#7a7a7a/#767471 → contrast 4.20-4.29 fails + * WCAG 2.1 AA 4.5:1. Q.DEFER.002 fix (12.05.2026 audit): локально bump до 0.7 + * → rendered ≈#595959 → 7.9:1+. + */ +.v-messages, +.v-field-label { + --v-medium-emphasis-opacity: 0.7; +} diff --git a/app/resources/js/components/admin/tenants/TenantsTable.vue b/app/resources/js/components/admin/tenants/TenantsTable.vue index 09e83827..a9bf71df 100644 --- a/app/resources/js/components/admin/tenants/TenantsTable.vue +++ b/app/resources/js/components/admin/tenants/TenantsTable.vue @@ -86,6 +86,7 @@ function statusColor(s: TenantStatus): string { variant="text" size="small" density="comfortable" + :aria-label="`Войти как клиент (impersonation) для ${item.name}`" :disabled="item.status === 'suspended'" :data-testid="`impersonate-btn-${item.id}`" @click.stop="emit('impersonate', item)" diff --git a/app/resources/js/components/billing/TransactionsTable.vue b/app/resources/js/components/billing/TransactionsTable.vue index 7202fffd..81093f4f 100644 --- a/app/resources/js/components/billing/TransactionsTable.vue +++ b/app/resources/js/components/billing/TransactionsTable.vue @@ -93,7 +93,7 @@ const filteredTransactions = computed(() => { color: #66635c; } .tx-amount-up { - color: #2e8b57; + color: #1b6e3b; } .tx-amount-down { color: #b83a3a; diff --git a/app/resources/js/components/dashboard/DashboardBalance.vue b/app/resources/js/components/dashboard/DashboardBalance.vue index 436b1ee6..bbbf9b0f 100644 --- a/app/resources/js/components/dashboard/DashboardBalance.vue +++ b/app/resources/js/components/dashboard/DashboardBalance.vue @@ -29,7 +29,7 @@ defineProps<{  ₽
-
+