8 lines
17 KiB
JSON
8 lines
17 KiB
JSON
{
|
||
"index": 202,
|
||
"slug": "editorial-2022-05-field-design-system",
|
||
"title": "Как менять токен дизайн-системы и не сломать соседний экран",
|
||
"excerpt": "Практический разбор правки токена: как зафиксировать симптом, оценить радиус изменения, проверить состояния кнопки и оставить точный путь к откату.",
|
||
"contentHtml": "<p>Цвет primary-кнопки меняют в одной строке, а ошибка появляется на другом экране. На форме оплаты пропадает focus ring. В диалоге отмены кнопка получает цвет действия подтверждения. В третьем месте новый токен не применяется, потому что компонент ждёт другое имя. Визуально это похоже на одну проблему. Технически это разные сбои контракта.</p>\n<p>Цена ошибки растёт вместе с радиусом общего токена. Локальная правка исправляет один usage. Глобальная правка меняет все usages, включая те, которые не попали в поиск. Если команда не знает список мест и состояний, она не может объяснить diff, выбрать безопасный откат или отличить новый вариант от случайного исключения.</p>\n<p><strong>Тезис.</strong> Токен нельзя менять по одному скриншоту. Сначала свяжите симптом с ролью компонента, состоянием, именем токена и известными usages. Затем проверьте допустимость значения. Только после этого меняйте один слой и сохраняйте прежнее значение. Такой порядок делает изменение ограниченным, наблюдаемым и обратимым.</p>\n<h2>Механизм: токен имеет владельца и радиус</h2>\n<p>Дизайн-токен — не просто цветовая константа. Он выражает роль: например, фон primary-кнопки в обычном состоянии. Роль связывает значение с компонентом и состоянием. Если код использует <code>#2457D6</code> напрямую, он обходит эту связь. Если код ссылается на неизвестное имя, система получает второй, неописанный способ задать ту же роль.</p>\n<p>У одного значения есть четыре границы. Первая — имя роли, например <code>button.primary.background</code>. Вторая — состояние: <code>default</code>, <code>hover</code>, <code>focus-visible</code>, <code>disabled</code> или <code>loading</code>. Третья — компонент, который действительно может использовать эту роль. Четвёртая — набор известных мест в коде. Пропуск любой границы превращает косметическую правку в догадку.</p>\n<p>Состояния нельзя восстановить из одного цвета. Кнопка может сохранить фон и потерять outline при переходе на клавиатуру. Она может выглядеть одинаково в спокойном состоянии, но стать неразличимой при отключении. Поэтому inventory должен хранить не только имя токена, но и ожидаемые состояния. Для ссылки, переключателя и destructive-действия нужен отдельный контракт, а не необязательный флаг в универсальной кнопке.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<table><caption>Минимальная диагностика разрыва контракта</caption><thead><tr><th>Симптом</th><th>Причина</th><th>Проверка</th><th>Действие</th></tr></thead><tbody><tr><td>На одном экране другой синий</td><td>Literal обошёл именованный токен</td><td>Найти значение и сравнить с ролью в inventory</td><td>Заменить подтверждённый usage</td></tr><tr><td>После refactor исчез focus ring</td><td>Состояние не входит в матрицу</td><td>Пройти кнопку клавиатурой</td><td>Вернуть <code>focus-visible</code> в контракт</td></tr><tr><td>Новое имя не работает</td><td>Имя отсутствует в словаре роли</td><td>Проверить декларацию и чтение</td><td>Остановить правку или добавить роль</td></tr><tr><td>Review назван visual-проверкой без снимка</td><td>Список входов перепутали с результатом</td><td>Проверить браузер, viewport и diff</td><td>Не выдавать зелёный статус без артефакта</td></tr><tr><td>Платёжный экран изменился вместе с профилем</td><td>Общий токен исправляли без радиуса</td><td>Сопоставить usages и роли</td><td>Вернуть прежнее значение и отделить variant</td></tr></tbody></table>\n<h2>Инвентарь до правки</h2>\n<p>Начните с короткой таблицы известных usages. Для каждого места запишите компонент, роль, состояние, имя и способ задания значения. Например, <code>profile-save</code> использует <code>button.primary.background</code> в состояниях <code>default</code>, <code>hover</code>, <code>focus-visible</code>, <code>disabled</code> и <code>loading</code>. <code>dialog-cancel</code> может быть secondary-кнопкой. Похожая разметка не делает эти usages одной ролью.</p>\n<p>Поиск по имени и hex-значению даёт кандидатов, но не доказывает полноту inventory. Стиль может прийти из темы, CSS-переменной, inline-значения или обёртки. Автоматический поиск не определяет, является ли ссылка кнопкой по смыслу. Поэтому результат поиска нужно проверить на уровне entry point и фактического состояния. Учебный пример ниже ограничен тремя usages и не изображает полный обход настоящего репозитория.</p>\n<h2>Проверка входа раньше изменения</h2>\n<p>Неверная конфигурация должна остановиться до записи. В примере есть словарь разрешённых ролей и простая проверка формата: цвет передаётся как <code>#RRGGBB</code>. Это учебное правило, а не универсальный валидатор CSS. Его задача — не дать срочной правке молча создать новый ключ или принять случайную строку.</p>\n<pre><code>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);</code></pre>\n<p>Функция не меняет объект сама. Она возвращает решение с именем, прежним и новым значением. Отдельный слой применяет решение после проверки inventory. Валидатор не знает, подходит ли новый синий для оплаты, профиля или тёмной темы. Он проверяет только форму и существование роли.</p>\n<p>В учебном коде <code>change.nextValue</code> равен <code>#1D4ED8</code>, а <code>restored.nextValue</code> равен <code>#2457D6</code>. Пример не запускает CSS, не открывает браузер и не измеряет контраст. Он показывает только две операции: неизвестное имя отклоняется, а принятая правка хранит точное прежнее значение. В настоящем проекте формат, запись и права должны соответствовать его контракту.</p>\n<figure><img src='/assets/editorial/2022/design-system-diagnosis-rollback-2022.svg' alt='Схема изменения токена: inventory ведёт к проверке роли и состояния, неверная конфигурация останавливается, допустимая правка сохраняет прежнее значение для отката' /><figcaption>Диагностический маршрут токена. Схема показывает порядок проверки и возврата значения; она не является результатом visual-регрессионного запуска.</figcaption></figure>\n<h2>Почему список входов не равен проверке интерфейса</h2>\n<p>До реального запуска можно составить список входов: viewport 375 и 1280, состояния <code>default</code>, <code>hover</code>, <code>focus-visible</code>, <code>disabled</code>, <code>loading</code>, светлая и тёмная темы. Список полезен как граница проверки: он заставляет назвать объём работы и не забыть состояние.</p>\n<p>Но список не видит пиксели, cascade, media query, шрифт или порядок фокуса. Заполненный JSON не доказывает, что браузер применил нужную переменную. Для visual-проверки нужны страница, браузер, viewport, baseline, новый снимок, правило допустимого diff и сохранённый результат. Для доступности нужны keyboard route и выбранная комбинация браузера и вспомогательной технологии. Если артефакта нет, проверку нельзя называть успешной.</p>\n<h2>Порядок исправления</h2>\n<ol><li><strong>Зафиксируйте симптом.</strong> Укажите экран, компонент, состояние, ожидаемое и фактическое значение.</li><li><strong>Определите роль.</strong> Проверьте, что control действительно primary-кнопка. Для другого смысла нужен отдельный контракт.</li><li><strong>Соберите inventory.</strong> Найдите usages по имени, роли и literal-значению. Отметьте подтверждённые места.</li><li><strong>Проверьте состояния.</strong> Пройдите клавиатурой focus-visible, затем проверьте disabled и loading. Default не заменяет остальные состояния.</li><li><strong>Проверьте вход.</strong> Отклоните неизвестный token и неверный формат. Не добавляйте новый ключ как временное исключение.</li><li><strong>Сделайте одну правку.</strong> Сохраните previous value, измените выбранную роль и запишите причину.</li><li><strong>Проверьте отрицательный путь.</strong> Передайте неизвестное имя, неверное значение и отмену принятой правки. Ни один путь не должен менять baseline молча.</li><li><strong>Проверьте интерфейс.</strong> Соберите CSS, откройте нужные viewport, пройдите клавиатурой и сохраните снимок или иной артефакт.</li></ol>\n<h2>Ограничения</h2>\n<p>Этот подход не вычисляет контраст и не заменяет ручную проверку. Он не решает каскад тем, локализацию, media queries, пользовательские настройки, разные браузеры и конкурирующие изменения. Он также не определяет, является ли новый цвет хорошим дизайном. Он отвечает на более узкий вопрос: существует ли роль, что именно она затрагивает и можно ли вернуть прежнее значение.</p>\n<p>Один общий токен не всегда лучше двух. Если профиль и оплата имеют разные требования к смыслу, состояниям или риску ошибки, их нужно разделить после подтверждения usages. Нельзя объявлять variant только потому, что один экран случайно выглядит иначе. Сначала исключите literal, неправильное состояние и неверное имя.</p>\n<p>Rollback не безопасен во всех слоях. Возврат значения токена не отменяет опубликованный CSS, не откатывает сборку и не компенсирует действие пользователя. Для этих уровней нужны собственные процедуры и артефакты. Здесь rollback ограничен одной парой <code>name → previousValue</code>.</p>\n<h2>Критерий готовности</h2>\n<p>Правка готова, если команда может показать inventory затронутых usages, матрицу состояний и запись с прежним значением. Неизвестный token и неверный формат останавливаются без изменения. Допустимая правка меняет только выбранную роль. Отдельный запуск подтверждает нужные viewport и состояния реальным артефактом. Повторный просмотр возвращает прежнее значение по сохранённому <code>previousValue</code>, а не по памяти или догадке.</p>\n<p>Если visual или accessibility запуск ещё не выполнен, критерий должен прямо это показывать. Нельзя превращать объявленный объём проверки в результат. Для учебного примера достаточно проверить решения функции и отрицательные входы. Для своего интерфейса добавьте браузерную проверку, историю baseline и ссылку на сохранённый diff.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href='https://www.w3.org/TR/WCAG22/' target='_blank' rel='noopener noreferrer'>W3C: Web Content Accessibility Guidelines (WCAG) 2.2</a> — требования к видимости фокуса, контрасту и состояниям интерфейса.</li><li><a href='https://www.w3.org/TR/css-variables-1/' target='_blank' rel='noopener noreferrer'>W3C: CSS Custom Properties for Cascading Variables Module Level 1</a> — область применения и вычисление пользовательских CSS-свойств.</li><li><a href='https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element' target='_blank' rel='noopener noreferrer'>WHATWG HTML Standard: The button element</a> — семантика нативной кнопки и её состояния в HTML.</li></ul>"
|
||
}
|