Обработка ошибок и UX-объяснения
1) Зачем это важно
Ошибка — это не «красный текст», а продолжение сценария. Хороший UX ошибок:- объясняет что случилось и что делать дальше,
- сохраняет введенные данные и предотвращает потерю прогресса,
- дает безопасный повтор или альтернативный путь,
- остается доступным (SR/клавиатура) и не раскрывает лишнего.
2) Типология ошибок (для интерфейса)
1. Валидация данных (4xx client): пустые/неверные поля, формат, длина, конфликт правил.
2. Бизнес-правила: лимиты, гео-ограничения, KYC/KYB, дубли, недоступные слоты.
3. Права/пермиссии: роль, доступ к ресурсу, возрастные ограничения.
4. Сеть/сервер: таймаут, оффлайн, 5xx, перегрузка, rate limit.
5. Конфликты/состояние: 409/412 (данные изменились), гонки, блокировки.
6. Отсутствие ресурса: 404/410, удалено/перенесено.
7. Платежные и рисковые: отклонение банком/PSP, антифрод, лимиты ответственной игры.
3) Каналы и уровень отображения
Подбираем «громкость» под контекст:Правило: не прячьте критичное в тосты/hover. Там, где пользователь смотрит, там и сообщение.
4) Копирайтинг ошибок
Структура: причина → следствие → действие.
Тон: честный, нейтральный, без вины.
Конкретика: укажите поле/условие, избегайте кодов и стеков.
Кнопка-действие: «Повторить», «Изменить карту», «Сбросить фильтры», «Открыть чат».
Чувствительные данные: не показывать (маскировка PAN, личные атрибуты).
Примеры
Хорошо: «Платеж не прошел: банк отклонил операцию. Попробуйте другой способ или повторите позже».
Плохо: «Ошибка 500. Что-то пошло не так».
Хорошо: «Лимит дневных расходов достигнут. Установите новый лимит или попробуйте завтра».
Хорошо: «Файл слишком большой (макс. 25 МБ). Сожмите или загрузите несколько файлов».
5) Поведение и фокус (A11y)
Ошибка выводится в фокусный контекст: переводим фокус к первому ошибочному полю.
Живые регионы: `role="status"` (polite) для инфо, `role="alert"` (assertive) — для критичных.
Видимое `:focus-visible`, контраст ≥ AA, альтернативы цвету (иконка/текст).
Сообщение привязываем к полю через `aria-describedby`.
html
<label for = "pwd "> Password </label>
<input id="pwd" name="password" aria-describedby="pwd-err" aria-invalid="true">
<p id = "pwd-err" role = "alert"> Minimum 8 characters </p>
6) Ретраи, backoff и идемпотентность
Повтор предлагается, если шанс успеха есть (сетевые сбои, 5xx, rate limit).
Экспоненциальный backoff 1–2–4–8 с, лимит попыток, понятная кнопка «Повторить».
Критичные операции (ставки/платежи): обязательный Idempotency-Key → исключаем дубли.
Откаты оптимистичных обновлений — четкий визуальный возврат + пояснение.
js async function retry(fn, attempts=3){
let wait=1000; for(let i=0; i<attempts; i++){
try{ return await fn(); }catch(e){ if(i===attempts-1) throw e; await new Promise(r=>setTimeout(r,wait)); wait=2; }
}
}
7) Оффлайн, таймауты и частичный контент
Оффлайн: показываем баннер «Нет подключения», доступ к кешу (read-only), очередь синхронизации.
Таймауты: UI-таймаут (3–5 с) → состояние «Ожидаем подтверждение…» с безопасным повтором/отменой.
Частичный успех: сохраняем то, что удалось; маркируем «не синхронизировано».
8) Конфликты и конкурентность
409/412: данные устарели. Предложить «Обновить» и показать дифф (что изменилось).
Блокировки: информируем, кто держит блок, и сколько времени, кнопка «Запросить доступ».
9) Образцы UI-шаблонов
Баннер страницы:html
<div class="banner banner--error" role="alert">
<strong> Connection failed. </strong> Shows cached data.
<button class =" btn btn--ghost" id = "retry "> Retry </button>
</div>
Модалка критической ошибки:
html
<div role="alertdialog" aria-labelledby="err-title" aria-describedby="err-desc">
<h2 id = "err-title "> Session expired </h2>
<p id = "err-desc "> Sign in again to continue. </p>
<button class = "btn "> Sign in </button>
<button class =" btn btn--ghost"> Home </button>
</div>
React ErrorBoundary (с ID корреляции):
tsx function Fallback({ id, onRetry }: { id: string; onRetry: ()=>void }) {
return (
<div role="alert" className="banner banner--error">
<strong> We couldn't load the page. </strong>
<div> Try again. Код: <code>{id}</code> <button onClick={()=>navigator. clipboard. writeText (id)}> Copy </button> </div>
<button onClick = {onRetry}> Retry </button>
</div>
);
}
10) Токены ошибок (дизайн-система)
json
{
"error": {
"tones": { "danger": "#", "warning": "#", "info": "#" },
"aria": { "polite": true, "assertive": true },
"timing": { "toastMs": 3500, "retryBackoffMs": [1000,2000,4000] },
"layout": { "fieldGap": 8, "bannerIcon": 20 }
}
}
CSS-пресеты:
css
.banner--error { background: var(--bg-danger); color: var(--on-danger); padding: 12px 16px; border-radius: 12px; }
.field-error { color: var(--role-danger); margin-top: 6px; font-size:.875rem; }
11) Безопасность и приватность
Не выводим стек-трейсы, внутренние ID, пути БД.
Маскируем чувствительные значения (карты, документы).
Сообщения не должны подсказывать злоумышленнику (например, что аккаунт существует).
Для поддержки — ID корреляции вместо деталей.
json
{"level":"error","event":"payment_fail","correlation_id":"c-8f1...","user_id":"u-","route":"/pay","psp_code":"DO_NOT_EXPOSE_TO_USER"}
12) Метрики и контроль
INP и доля Long Tasks в момент ошибки (ошибка не должна «вешать» UI).
Retry success rate, ошибки на 1000 действий, время до восстановления.
CTR на «Помощь/Чат», процент брошенных форм.
Тепловые карты: где чаще всего возникают field-errors.
13) QA-чек-лист
Доступность
- Фокус на первом ошибочном поле; `aria-describedby`/`aria-invalid` выставлены.
- Критичные сообщения — `role="alert"`; контраст ≥ AA.
Поведение
- Данные формы не теряются при ошибке.
- Есть понятный `Retry` и корректный backoff.
- Оффлайн-режим/кеш работают; баннер видим.
Копирайтинг
- Причина → действие; без технического жаргона и обвинений.
- Тексты локализуются и не ломают сетку.
Безопасность
- Нет утечки PII/секретов; показываем только безопасные коды/ID.
- Идемпотентность включена для критичных операций.
14) Специфика iGaming
Ставка:- UI сразу фиксирует `busy`; при задержке > 3 с — «Ожидаем подтверждение…».
- При fail: честный статус («рынок закрылся», «коэффициент изменился») + безопасный `Retry`.
- Идемпотентный ключ, чтобы исключить двойную ставку.
- Различаем «отказ банка/PSP» vs «сбой сервера». Для первого — «Выберите другой способ», для второго — `Retry`.
- Прозрачные шаги KYC/AML; ссылки «Почему это нужно?».
- Тон заботливый, без давления. «Лимит достигнут — сделайте паузу или обновите лимит».
- Без вспышек/неона; контраст AAA, доступность при SR.
- Четко объясните ограничения и предложите «Ознакомиться с правилами/поддержка».
15) Анти-паттерны
«Что-то пошло не так» без действий и контекста.
Сброс формы после ошибки.
Прятать критичное в тост на 3 секунды.
Только цвет без текста/иконки.
Бесконечные ретраи без возможности отмены.
Показывать внутренние коды/стек-трейсы.
16) Документация в дизайн-системе
Компоненты: `FieldError`, `FormError`, `PageBanner`, `AlertDialog`, `ErrorBoundary`.
Токены тонов/контраста/таймингов, пресеты a11y и примеры ARIA.
Карта типовых сценариев (валидация, сеть, права, платежи) с текстовыми шаблонами.
«Do/Don’t»: реальные экраны до/после с метриками отказов/успехов.
Краткое резюме
Делайте ошибки понятными и управляемыми: говорите человеческим языком, сохраняйте введенные данные, предлагайте безопасный повтор и альтернативы, уважайте доступность и приватность. Тогда даже нештатные ситуации сохраняют доверие и не прерывают путь пользователя — особенно в критичных сценариях ставок и платежей.