fix: WCAG AA contrast remediation on border/status colors (Z8, user-authorized)

Darkened --border-color and --success/--warning/--error/--info-color in
all three theme files, hue-preserving, computed against WCAG 2.1 formulas:
- border-color: 1.24-1.42:1 -> >=3.0:1 (non-text/UI-component minimum)
- status colors: 2.15-3.76:1 -> >=4.5:1 (plain-text minimum)
--primary-color/--secondary-color/--accent-color/gradients untouched -
only semantic feedback tokens changed. lavero's success-color now diverges
from primary-color (they only happened to share a hex before; semantic
status vs. brand identity are different concerns).

Also verified/corrected during this pass, doc was stale not code:
- Footer "Contacts" is not a code gap - Footer Builder + static-page CMS
  already resolve any authored page generically via pageKey
- Checkout payment-description fallback already tries brandName -> hostname
  -> i18n-translated fallback (en/ru/hy), not a hardcoded RU string

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-08-17 22:04:14 +04:00
parent 634e3faf3d
commit 54cd089e80
4 changed files with 36 additions and 22 deletions

View File

@@ -9,12 +9,12 @@ Findings only — nothing in this document has been fixed as part of writing it.
1. **FIXED (verified 2026-08-17).** ~~Ed25519 admin-auth "session expired"/"invalid signature" screens were dead UI~~`toAuthErrorShape()` now reads `error.error.code` via `authErrorCodeFromBackendCode()` before falling back to HTTP status. See `BACKEND-API-REFERENCE.md` §5. 1. **FIXED (verified 2026-08-17).** ~~Ed25519 admin-auth "session expired"/"invalid signature" screens were dead UI~~`toAuthErrorShape()` now reads `error.error.code` via `authErrorCodeFromBackendCode()` before falling back to HTTP status. See `BACKEND-API-REFERENCE.md` §5.
2. **FIXED (2026-08-17).** ~~Dark mode selector did nothing~~ — structural dark overrides (bg/text/border/shadow) now wired for all three tenant themes under `[data-theme-mode="dark"]`. Brand colors intentionally unchanged pending a theme-owner-approved dark palette. 2. **FIXED (2026-08-17).** ~~Dark mode selector did nothing~~ — structural dark overrides (bg/text/border/shadow) now wired for all three tenant themes under `[data-theme-mode="dark"]`. Brand colors intentionally unchanged pending a theme-owner-approved dark palette.
3. **FIXED (verified 2026-08-17).** ~~"Site Layout" selector had no effect~~`SectionEngineService.resolveLayoutType()` now falls back to `bootstrap.layout.type` when a page has no layout of its own. 3. **FIXED (verified 2026-08-17).** ~~"Site Layout" selector had no effect~~`SectionEngineService.resolveLayoutType()` now falls back to `bootstrap.layout.type` when a page has no layout of its own.
4. **Footer "Contacts" link has nothing behind it.** No static-page content exists for it at all in the bootstrap data (unlike other footer legal pages, which are populated). 4. **Not a code gap (re-verified 2026-08-17), a content gap.** The mechanism is already fully generic: `features/project-editor/sections/footer-section.component.ts`'s Footer Builder lets an admin create any static page via the CMS and link it into a footer column by `pageKey`, resolved by `FooterResolverService`. "Contacts" just has no authored static page yet on whichever tenant's bootstrap this was checked against — that's a per-tenant content task, not a frontend fix.
5. **FIXED (verified 2026-08-17).** ~~Product pages got no per-product SEO~~`SeoService.setItemMeta(item)` is called from `product-details-container.component.ts`. 5. **FIXED (verified 2026-08-17).** ~~Product pages got no per-product SEO~~`SeoService.setItemMeta(item)` is called from `product-details-container.component.ts`.
6. **FIXED (verified 2026-08-17).** ~~`og:locale` was hardcoded to `ru_RU`~~ — reads `languageService.currentLanguage()` via `OG_LOCALE_MAP` at both call sites. 6. **FIXED (verified 2026-08-17).** ~~`og:locale` was hardcoded to `ru_RU`~~ — reads `languageService.currentLanguage()` via `OG_LOCALE_MAP` at both call sites.
7. **FIXED (verified 2026-08-17) on JSON-LD; sitemap remains backend-only work.** ~~No structured data (JSON-LD) exists anywhere~~`SeoService.setJsonLd()` injects a real `<script type="application/ld+json">` for `Product` (per-item, via `setItemMeta()`) and `Organization` (site default, via `resetToDefaults()`). No JSON-LD exists yet for `BreadcrumbList` or `ItemList`/category pages — smaller net-new addition if wanted. Sitemap generation is still backend-only, unchanged. 7. **FIXED (verified 2026-08-17) on JSON-LD; sitemap remains backend-only work.** ~~No structured data (JSON-LD) exists anywhere~~`SeoService.setJsonLd()` injects a real `<script type="application/ld+json">` for `Product` (per-item, via `setItemMeta()`) and `Organization` (site default, via `resetToDefaults()`). No JSON-LD exists yet for `BreadcrumbList` or `ItemList`/category pages — smaller net-new addition if wanted. Sitemap generation is still backend-only, unchanged.
8. **Checkout's payment-description fallback is a hardcoded Russian string** (`'Покупка на Маркетплейсе'`) used as a last resort when no brand name or hostname is available — single-tenant-framed wording in a multi-tenant product. 8. **FIXED (verified 2026-08-17).** ~~Checkout's payment-description fallback was a hardcoded Russian string~~`getPaymentDescription()` (`pages/cart/cart.component.ts:613`) already tries `branding.brandName`, then hostname, and only falls to `i18n.t('cart.paymentDescriptionFallback')` last, translated in all 3 languages (`i18n/{en,ru,hy}.ts`).
9. **Brand color contrast fails WCAG AA.** `--border-color` measures 1.241.42:1 against a 3:1 UI-component requirement in every theme; `--success`/`--warning`/`--error`/`--info-color` fail 4.5:1 when used as plain text. Real palette colors, not a token bug — see [Accessibility](#as-accessibility-reviewer). 9. **FIXED (2026-08-17), user-authorized.** ~~Brand color contrast failed WCAG AA~~ `--border-color` and `--success`/`--warning`/`--error`/`--info-color` darkened, hue-preserving, in all three theme files to clear 3:1 (border, non-text) and 4.5:1 (status colors, plain text). See [Accessibility](#as-accessibility-reviewer).
10. **FIXED (verified 2026-08-17).** ~~`stars.component.scss:10` used a literal hex color~~ — now uses `var(--border-color)`. 10. **FIXED (verified 2026-08-17).** ~~`stars.component.scss:10` used a literal hex color~~ — now uses `var(--border-color)`.
11. **No multi-vendor cart handling exists.** Checkout is one inline flow producing exactly one order from one payment popup; a cart with items from multiple sellers has no defined behavior (relevant the moment Seller Management ships beyond its current disabled-by-default placeholder). 11. **No multi-vendor cart handling exists.** Checkout is one inline flow producing exactly one order from one payment popup; a cart with items from multiple sellers has no defined behavior (relevant the moment Seller Management ships beyond its current disabled-by-default placeholder).
@@ -64,7 +64,7 @@ See [BACKEND-API-REFERENCE.md](BACKEND-API-REFERENCE.md) for the full contract.
## As Accessibility Reviewer ## As Accessibility Reviewer
1. **Brand color contrast genuinely fails WCAG AA** — see [Product Owner item 9 above](#as-product-owner--business) for the numbers. This requires a theme-owner sign-off before any fix ships, since it changes brand appearance, not just token values. 1. **FIXED (2026-08-17), user-authorized.** ~~Brand color contrast failed WCAG AA~~ — see [Product Owner item 9 above](#as-product-owner--business). Applied a hue-preserving darkening of the failing tokens rather than a redesign; a distinct dark-mode-specific status palette (introduced alongside dark mode this session) has not been separately contrast-checked and remains open.
2. **No screen-reader software testing has ever been performed on this codebase** — every existing accessibility verification (including in this review) is automated accessibility-tree inspection, never a real NVDA/VoiceOver session. Recommend at least one manual pass on the highest-traffic flows (checkout, product page, admin login) before treating any part of the app as accessibility-verified end to end. 2. **No screen-reader software testing has ever been performed on this codebase** — every existing accessibility verification (including in this review) is automated accessibility-tree inspection, never a real NVDA/VoiceOver session. Recommend at least one manual pass on the highest-traffic flows (checkout, product page, admin login) before treating any part of the app as accessibility-verified end to end.
3. **Known past pattern worth re-checking elsewhere:** a raw `<textarea>` (no dedicated shared textarea component exists in the codebase) previously shipped without its `aria-label`/label association wired correctly in one place (Seller Management's Message field, since fixed). Any other raw `<textarea>` usage in the app should be checked for the same gap, since the shared `app-input` component handles this automatically but plain textareas do not. 3. **Known past pattern worth re-checking elsewhere:** a raw `<textarea>` (no dedicated shared textarea component exists in the codebase) previously shipped without its `aria-label`/label association wired correctly in one place (Seller Management's Message field, since fixed). Any other raw `<textarea>` usage in the app should be checked for the same gap, since the shared `app-input` component handles this automatically but plain textareas do not.

View File

@@ -20,12 +20,16 @@
--bg-tertiary: #f0f0f0; --bg-tertiary: #f0f0f0;
--bg-header: rgba(117, 121, 124, 0.1); --bg-header: rgba(117, 121, 124, 0.1);
--success-color: #10b981; // WCAG AA remediation (2026-08-17): darkened, hue-preserving, to clear 4.5:1
--warning-color: #f59e0b; // as plain text against --bg-primary. Originals measured 2.15-3.76:1.
--error-color: #ef4444; --success-color: #0c855d;
--info-color: #3b82f6; --warning-color: #a36907;
--error-color: #eb1515;
--border-color: #d3dad9; --info-color: #1e6ff5;
// WCAG AA remediation (2026-08-17): darkened to clear 3:1 non-text/UI-component
// contrast against --bg-primary. Original measured 1.42:1.
--border-color: #859996;
--border-dark: #677b78; --border-dark: #677b78;
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.1); --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.1);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.15); --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.15);

View File

@@ -19,12 +19,18 @@
--bg-secondary: #f0fdf4; --bg-secondary: #f0fdf4;
--bg-tertiary: #d1fae5; --bg-tertiary: #d1fae5;
--success-color: #FD7300; // WCAG AA remediation (2026-08-17): darkened, hue-preserving, to clear 4.5:1
--warning-color: #f59e0b; // as plain text against --bg-primary. Originals measured 2.15-2.75:1.
--error-color: #ef4444; // success-color intentionally diverges from --primary-color here (semantic
--info-color: #3b82f6; // status token vs. brand token; they only happened to share a hex before).
--success-color: #c05700;
--border-color: #e5e7eb; --warning-color: #a36907;
--error-color: #eb1515;
--info-color: #1e6ff5;
// WCAG AA remediation (2026-08-17): darkened to clear 3:1 non-text/UI-component
// contrast against --bg-primary. Original measured 1.24:1.
--border-color: #8c95a6;
--border-dark: #4b5563; --border-dark: #4b5563;
--bg-header: rgba(253, 115, 0, 0.05); --bg-header: rgba(253, 115, 0, 0.05);
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.1); --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.1);

View File

@@ -19,12 +19,16 @@
--bg-secondary: #f0fdf4; --bg-secondary: #f0fdf4;
--bg-tertiary: #d1fae5; --bg-tertiary: #d1fae5;
--success-color: #10b981; // WCAG AA remediation (2026-08-17): darkened, hue-preserving, to clear 4.5:1
--warning-color: #f59e0b; // as plain text against --bg-primary. Originals measured 2.15-3.76:1.
--error-color: #ef4444; --success-color: #0c855d;
--info-color: #3b82f6; --warning-color: #a36907;
--error-color: #eb1515;
--border-color: #e5e7eb; --info-color: #1e6ff5;
// WCAG AA remediation (2026-08-17): darkened to clear 3:1 non-text/UI-component
// contrast against --bg-primary. Original measured 1.24:1.
--border-color: #8c95a6;
--border-dark: #4b5563; --border-dark: #4b5563;
--bg-header: rgba(16, 185, 129, 0.05); --bg-header: rgba(16, 185, 129, 0.05);
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.1); --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.1);