Бетслип удобнее рассматривать как транзакционный интерфейс, а не как форму с кнопкой. Каждый исход может изменить цену, стать недоступным или перейти в suspend независимо от остальных, поэтому глобальный статус купона быстро скрывает реальную причину ошибки.
Ошибка должна принадлежать конкретному исходу
Если один элемент комбинированной ставки больше недоступен, пользователю важно увидеть именно этот элемент и причину. Общий alert заставляет заново проверять весь список и повышает риск случайного действия. Локальная ошибка позволяет удалить проблемный исход или дождаться восстановления, не разрушая остальное состояние.
Сумма, итог и изменившаяся цена — разные данные
Интерфейс не должен визуально смешивать введённое значение, расчётный итог и пересчитанные коэффициенты. При любом изменении условий полезно подсветить источник пересчёта и запретить незаметное подтверждение старого намерения на новых данных.
Доступность тоже влияет на корректность
Маленькие tap-targets, неочевидная типографика статуса и отсутствие корректного чтения screen reader превращают формально правильную модель состояний в небезопасный интерфейс. Проверка должна включать случайные повторные нажатия, клавиатурную навигацию и сценарий возврата из фона.
Сценарий для приёмки
Возьмите комбинированный купон, сделайте один рынок недоступным между открытием экрана и подтверждением и проверьте три вещи: проблемный исход найден сразу, итог не пересчитывается молча, пользователь может безопасно продолжить с оставшимися элементами. Это модельный сценарий, а не результат пользовательского исследования.
Почему бетслип стоит проектировать как конечный автомат
Бетслип живёт дольше одного запроса: пользователь выбирает исход, получает актуальную цену, меняет сумму, подтверждает действие и может столкнуться с изменением рынка между этими шагами. Если состояния не названы явно, фронтенд начинает выводить их из набора флагов, а разные экраны трактуют одну ситуацию по-разному. Отдельные состояния для изменения цены, приостановки рынка, недоступности исхода и повторного подтверждения делают поведение предсказуемым.
Контрольные вопросы
- фиксировать версию или время котировки, с которой пользователь начал подтверждение
- разделять локальную валидацию суммы и ответ сервера о состоянии рынка
- делать повторную отправку идемпотентной и не создавать дублирующее действие
- сохранять понятную причину отказа для журнала и поддержки
Хороший UX здесь не скрывает изменение, а помогает человеку понять, что именно произошло. Если цена изменилась, интерфейс должен показать новое значение и запросить осознанное подтверждение; если рынок закрыт, старую цену нельзя оставлять как будто она всё ещё доступна.
Одно и то же состояние может открыться на двух устройствах
Race condition возникает не только внутри браузера. Пользователь может открыть betslip на телефоне и ноутбуке, а затем изменить выбор или подтвердить действие почти одновременно. Клиентские копии не должны самостоятельно решать, какая версия «победила». Нужен серверный operation/state version и понятный ответ на конфликт: принять актуальную версию, запросить повторное подтверждение или отклонить устаревшую попытку. Такая схема также упрощает восстановление после background/resume на мобильном устройстве.
Где чаще всего ломается ощущение «одной операции»
Пользователь видит один купон, но внутри обычно есть несколько шагов: чтение текущей цены, локальная проверка выбора, серверная валидация, возможный reprice, подтверждение и финальная фиксация результата. Ошибка возникает, когда интерфейс склеивает эти этапы в один optimistic state. Тогда кнопка уже выглядит успешно нажатой, а authoritative ответ ещё не пришёл. Практичнее явно различать «отправлено», «проверяется», «цена изменилась», «рынок недоступен» и «принято». Такой язык снижает риск двойного действия и делает спорную ситуацию объяснимой без чтения сетевого лога.
Состояние купона полезно сверять с тем, как устроен путь данных от фида до betting-клиента: если версия market state известна только серверу, клиент не должен изображать окончательность раньше ответа. При reconnect важно не «дорисовывать» прошлый optimistic result, а запросить подтверждённое состояние операции и сопоставить его с локальным request id. Это особенно заметно на нестабильной мобильной сети, где повторное нажатие и повторная доставка запроса могут произойти почти одновременно.
Ещё одна практическая проверка — намеренно менять цену между открытием купона и подтверждением. Хороший интерфейс не прячет этот момент за общей ошибкой, а показывает, что именно поменялось и какое действие требуется от пользователя. Такая проверка полезнее демонстрации happy path, потому что показывает реальную границу между UX и транзакционным состоянием.
Перед выпуском нового betslip-flow полезно прогнать его не только на стабильном Wi‑Fi. Нужны обрыв сети после отправки, повторное открытие приложения, два быстрых нажатия, reprice между запросом и ответом и возврат сервера с уже обработанным request id. В каждом сценарии пользователь должен получить однозначное состояние, а support — возможность найти ту же операцию по техническому идентификатору. Если для объяснения результата требуется догадываться, какой из двух запросов «был настоящим», state machine ещё недостаточно строгая.
