{ "index": 202, "slug": "editorial-2022-05-field-design-system", "title": "Как менять токен дизайн-системы и не сломать соседний экран", "excerpt": "Практический разбор правки токена: как зафиксировать симптом, оценить радиус изменения, проверить состояния кнопки и оставить точный путь к откату.", "contentHtml": "

Цвет primary-кнопки меняют в одной строке, а ошибка появляется на другом экране. На форме оплаты пропадает focus ring. В диалоге отмены кнопка получает цвет действия подтверждения. В третьем месте новый токен не применяется, потому что компонент ждёт другое имя. Визуально это похоже на одну проблему. Технически это разные сбои контракта.

\n

Цена ошибки растёт вместе с радиусом общего токена. Локальная правка исправляет один usage. Глобальная правка меняет все usages, включая те, которые не попали в поиск. Если команда не знает список мест и состояний, она не может объяснить diff, выбрать безопасный откат или отличить новый вариант от случайного исключения.

\n

Тезис. Токен нельзя менять по одному скриншоту. Сначала свяжите симптом с ролью компонента, состоянием, именем токена и известными usages. Затем проверьте допустимость значения. Только после этого меняйте один слой и сохраняйте прежнее значение. Такой порядок делает изменение ограниченным, наблюдаемым и обратимым.

\n

Механизм: токен имеет владельца и радиус

\n

Дизайн-токен — не просто цветовая константа. Он выражает роль: например, фон primary-кнопки в обычном состоянии. Роль связывает значение с компонентом и состоянием. Если код использует #2457D6 напрямую, он обходит эту связь. Если код ссылается на неизвестное имя, система получает второй, неописанный способ задать ту же роль.

\n

У одного значения есть четыре границы. Первая — имя роли, например button.primary.background. Вторая — состояние: default, hover, focus-visible, disabled или loading. Третья — компонент, который действительно может использовать эту роль. Четвёртая — набор известных мест в коде. Пропуск любой границы превращает косметическую правку в догадку.

\n

Состояния нельзя восстановить из одного цвета. Кнопка может сохранить фон и потерять outline при переходе на клавиатуру. Она может выглядеть одинаково в спокойном состоянии, но стать неразличимой при отключении. Поэтому inventory должен хранить не только имя токена, но и ожидаемые состояния. Для ссылки, переключателя и destructive-действия нужен отдельный контракт, а не необязательный флаг в универсальной кнопке.

\n

Симптом → причина → проверка → действие

\n
Минимальная диагностика разрыва контракта
СимптомПричинаПроверкаДействие
На одном экране другой синийLiteral обошёл именованный токенНайти значение и сравнить с ролью в inventoryЗаменить подтверждённый usage
После refactor исчез focus ringСостояние не входит в матрицуПройти кнопку клавиатуройВернуть focus-visible в контракт
Новое имя не работаетИмя отсутствует в словаре ролиПроверить декларацию и чтениеОстановить правку или добавить роль
Review назван visual-проверкой без снимкаСписок входов перепутали с результатомПроверить браузер, viewport и diffНе выдавать зелёный статус без артефакта
Платёжный экран изменился вместе с профилемОбщий токен исправляли без радиусаСопоставить usages и ролиВернуть прежнее значение и отделить variant
\n

Инвентарь до правки

\n

Начните с короткой таблицы известных usages. Для каждого места запишите компонент, роль, состояние, имя и способ задания значения. Например, profile-save использует button.primary.background в состояниях default, hover, focus-visible, disabled и loading. dialog-cancel может быть secondary-кнопкой. Похожая разметка не делает эти usages одной ролью.

\n

Поиск по имени и hex-значению даёт кандидатов, но не доказывает полноту inventory. Стиль может прийти из темы, CSS-переменной, inline-значения или обёртки. Автоматический поиск не определяет, является ли ссылка кнопкой по смыслу. Поэтому результат поиска нужно проверить на уровне entry point и фактического состояния. Учебный пример ниже ограничен тремя usages и не изображает полный обход настоящего репозитория.

\n

Проверка входа раньше изменения

\n

Неверная конфигурация должна остановиться до записи. В примере есть словарь разрешённых ролей и простая проверка формата: цвет передаётся как #RRGGBB. Это учебное правило, а не универсальный валидатор CSS. Его задача — не дать срочной правке молча создать новый ключ или принять случайную строку.

\n
const tokens = {'button.primary.background':'#2457D6'};\nfunction correctToken(name, nextValue) {\n  if (!(name in tokens)) return {ok:false, reason:'unknown-token'};\n  if (!/^#[0-9A-F]{6}$/i.test(nextValue)) return {ok:false, reason:'invalid-color'};\n  return {ok:true, name, previousValue:tokens[name], nextValue};\n}\nfunction rollbackToken(change) {\n  return {ok:change.ok, name:change.name, nextValue:change.previousValue};\n}\nconst change = correctToken('button.primary.background','#1D4ED8');\nconst restored = rollbackToken(change);
\n

Функция не меняет объект сама. Она возвращает решение с именем, прежним и новым значением. Отдельный слой применяет решение после проверки inventory. Валидатор не знает, подходит ли новый синий для оплаты, профиля или тёмной темы. Он проверяет только форму и существование роли.

\n

В учебном коде change.nextValue равен #1D4ED8, а restored.nextValue равен #2457D6. Пример не запускает CSS, не открывает браузер и не измеряет контраст. Он показывает только две операции: неизвестное имя отклоняется, а принятая правка хранит точное прежнее значение. В настоящем проекте формат, запись и права должны соответствовать его контракту.

\n
Схема изменения токена: inventory ведёт к проверке роли и состояния, неверная конфигурация останавливается, допустимая правка сохраняет прежнее значение для отката
Диагностический маршрут токена. Схема показывает порядок проверки и возврата значения; она не является результатом visual-регрессионного запуска.
\n

Почему список входов не равен проверке интерфейса

\n

До реального запуска можно составить список входов: viewport 375 и 1280, состояния default, hover, focus-visible, disabled, loading, светлая и тёмная темы. Список полезен как граница проверки: он заставляет назвать объём работы и не забыть состояние.

\n

Но список не видит пиксели, cascade, media query, шрифт или порядок фокуса. Заполненный JSON не доказывает, что браузер применил нужную переменную. Для visual-проверки нужны страница, браузер, viewport, baseline, новый снимок, правило допустимого diff и сохранённый результат. Для доступности нужны keyboard route и выбранная комбинация браузера и вспомогательной технологии. Если артефакта нет, проверку нельзя называть успешной.

\n

Порядок исправления

\n
  1. Зафиксируйте симптом. Укажите экран, компонент, состояние, ожидаемое и фактическое значение.
  2. Определите роль. Проверьте, что control действительно primary-кнопка. Для другого смысла нужен отдельный контракт.
  3. Соберите inventory. Найдите usages по имени, роли и literal-значению. Отметьте подтверждённые места.
  4. Проверьте состояния. Пройдите клавиатурой focus-visible, затем проверьте disabled и loading. Default не заменяет остальные состояния.
  5. Проверьте вход. Отклоните неизвестный token и неверный формат. Не добавляйте новый ключ как временное исключение.
  6. Сделайте одну правку. Сохраните previous value, измените выбранную роль и запишите причину.
  7. Проверьте отрицательный путь. Передайте неизвестное имя, неверное значение и отмену принятой правки. Ни один путь не должен менять baseline молча.
  8. Проверьте интерфейс. Соберите CSS, откройте нужные viewport, пройдите клавиатурой и сохраните снимок или иной артефакт.
\n

Ограничения

\n

Этот подход не вычисляет контраст и не заменяет ручную проверку. Он не решает каскад тем, локализацию, media queries, пользовательские настройки, разные браузеры и конкурирующие изменения. Он также не определяет, является ли новый цвет хорошим дизайном. Он отвечает на более узкий вопрос: существует ли роль, что именно она затрагивает и можно ли вернуть прежнее значение.

\n

Один общий токен не всегда лучше двух. Если профиль и оплата имеют разные требования к смыслу, состояниям или риску ошибки, их нужно разделить после подтверждения usages. Нельзя объявлять variant только потому, что один экран случайно выглядит иначе. Сначала исключите literal, неправильное состояние и неверное имя.

\n

Rollback не безопасен во всех слоях. Возврат значения токена не отменяет опубликованный CSS, не откатывает сборку и не компенсирует действие пользователя. Для этих уровней нужны собственные процедуры и артефакты. Здесь rollback ограничен одной парой name → previousValue.

\n

Критерий готовности

\n

Правка готова, если команда может показать inventory затронутых usages, матрицу состояний и запись с прежним значением. Неизвестный token и неверный формат останавливаются без изменения. Допустимая правка меняет только выбранную роль. Отдельный запуск подтверждает нужные viewport и состояния реальным артефактом. Повторный просмотр возвращает прежнее значение по сохранённому previousValue, а не по памяти или догадке.

\n

Если visual или accessibility запуск ещё не выполнен, критерий должен прямо это показывать. Нельзя превращать объявленный объём проверки в результат. Для учебного примера достаточно проверить решения функции и отрицательные входы. Для своего интерфейса добавьте браузерную проверку, историю baseline и ссылку на сохранённый diff.

\n

Проверяемые источники

\n" }