UI-задачу легко сформулировать словами "сделай красиво" и принять по одному скриншоту, на котором всё на месте. Именно так рождается интерфейс, который отлично выглядит ровно в одном состоянии и на одной ширине экрана - в том, что попал на скриншот. Всё остальное - пустые данные, ошибка сети, узкий телефон, длинный текст, клавиатура вместо мыши - остаётся непокрытым, потому что о нём не договаривались.
Наивный ход - оценивать интерфейс как картинку. Есть макет, есть результат, они похожи - задача выполнена. Эта модель переносит на живой UI логику статичного изображения, где нет ни состояний, ни ввода, ни разных экранов. Но интерфейс - не картинка, а поведение: одна и та же вёрстка проживает загрузку, пустоту, ошибку и успех, и "похоже на макет" описывает лишь один кадр из этой жизни.
Ломается это на всём, что не попало в happy path. Список, прекрасный на десяти элементах, ломается на нуле и на тысяче. Кнопка, аккуратная по-английски, разъезжается на длинной локализации. Форма, удобная мышью, оказывается непроходимой с клавиатуры. Верстка, выверенная на ноутбуке, наезжает сама на себя на 320 пикселях. Каждый из этих провалов невидим на единственном скриншоте и неизбежен в реальном использовании.
Отсюда понятие визуального контракта - того, что должно быть названо до правки, а не оценено после. В него входят: reference или макет, design tokens вместо магических значений, целевые viewport, обязательные состояния loading, empty и error, а также требования доступности. Отдельная строка контракта - переиспользовать существующие компоненты, а не плодить новые: это удерживает согласованность системы и не размывает те же токены новыми вариантами.
Почему состояния и токены важнее точного попиксельного совпадения. Пиксель-в-пиксель на одном экране - хрупкая цель: она ломается от смены данных и ширины. Контракт из состояний и токенов, наоборот, описывает поведение, которое переносимо: если loading, empty и error заданы и собраны из общих токенов, интерфейс держит форму на данных, которых художник макета вообще не рисовал. Договор о поведении переживает то, чего договор о картинке не предусмотрел.
Проверку UI даёт не воображение, а Browser Preview - и у него есть документированная особенность. Preview проксирует ваш локальный dev-сервер и позволяет выбирать элементы и захватывать ошибки консоли, но агент не видит страницу автоматически: вы сами отбираете, какой элемент и какую ошибку отправить ему как контекст. То есть визуальную обратную связь курирует человек - и качество проверки прямо зависит от того, показали ли вы агенту не только happy path, но и сломанные состояния.
Цена пропущенного контракта платится там, где вас нет, - на чужих устройствах и данных. Интерфейс, принятый по одному кадру, встречает пользователя пустым экраном без объяснения, нечитаемым контрастом, формой, из которой не выбраться табом, или вёрсткой, разъехавшейся на его телефоне. Эти дефекты не видны автору, потому что автор смотрел на свой экран со своими данными; их находит тот, у кого условия другие, и находит болезненно. И чинить их приходится потом вслепую: по скупому багрепорту с чужого устройства вместо собственного экрана, где всё это было бы видно сразу.
Это не значит, что каждую подпись нужно проводить через полный чек-лист. Точечная правка отступа или цвета внутри готового компонента проверяется взглядом на месте. Визуальный контракт окупается там, где появляется новое состояние, новый экран или ввод пользователя: чем больше у интерфейса состояний и чем шире разброс устройств, тем дороже обойтись без явно названного договора о поведении.
Проверять UI нужно по списку, а не по впечатлению, и список этот конкретен. Клавиатурный фокус и порядок обхода табом. Контраст и наличие accessible name у интерактивных элементов. Поведение на длинном тексте и другой локали. Три ширины - узкая около 320-390 пикселей, средняя и широкая. Ошибка сети и повторная отправка. Пройдите этот список в Preview, отправляя агенту не только успешные, но и сломанные состояния, - и "выглядит хорошо" превратится из впечатления в проверенный факт.
Типичные провалы повторяют пропущенные пункты контракта. Нет состояний - пустой экран вместо empty-state и белый экран вместо обработанной ошибки. Нет доступности - интерфейс, непроходимый с клавиатуры и невидимый скринридеру. Нет проверки ширин - вёрстка, живая только на экране автора. Нет курирования Preview - агент "починил" то, что и так работало, не увидев сломанного. Признак у всех один: результат приняли по happy path, не назвав остальных состояний. Назовите контракт заранее - и проверять будет что, а не чем любоваться.