Дизайн-аудит окупился как код-ревью

A11y-аудит дизайн-системы Сказии нашёл в проде то же, что находит код-ревью: контраст eyebrow 1.36:1, кнопку play 38px и статус, переданный одним цветом. И почему мультиагентность ускоряет мышление ролей, но не отрисовку.

1.36:1 — это не «бледновато», это баг

Eyebrow над карточкой «сегодня» в Mini App «Сказии» был синим primary на тёплом кремовом фоне. На скриншоте — нормально, в Figma — нормально. Контраст — 1.36:1. WCAG AA для мелкого текста требует 4.5:1. 1.36:1 — это не «на грани», это втрое ниже нормы. Текст, который для части людей просто не читается: при ярком солнце, на дешёвой матрице, при сниженном зрении. И это не где-то в углу — это `.today-eyebrow`, подпись над главным ежедневным экраном, тем самым, ради которого продукт открывают. Рядом нашлось ещё. Кнопка play в аудиобаре — 38×38px при минимуме 44×44 для тач-таргета. Палец промахивается, особенно когда включаешь сказку ребёнку одной рукой в темноте. Статус эпизода в серии передавался только цветом — зелёный «пройдено», серый «заблокировано», без символа и без подписи. Для дальтоника это два одинаковых серых кружка. И жёсткий `slice()`, резавший длинный статусный текст по символам, а не по словам — обрывая фразу на середине слова. Четыре находки. Все — в живом `tg/webapp/styles.css` и `apps/api/src/bot/text.ts`, а не в макете. То есть не «давай в Figma поправим» — это то, что уже едет в проде у пользователей. Глазами я их не нашёл. Их нашёл аудит, запущенный как процесс, а не как «глянь, что улучшить».

Аудит должен быть строгим, а не вкусовым

Обычный дизайн-«ревью» звучит так: «вот тут бы посветлее, тут отступ великоват». Это вкусовщина. Её нельзя проверить, закрыть или передать — через неделю спорить о ней будут заново. Код-ревью устроено иначе. Там есть критерий: тест красный или зелёный, контракт соблюдён или нарушен. Находка либо подтверждается, либо отбраковывается. У неё есть severity, файл и строка, есть «готово, когда…». Я хотел того же от дизайн-аудита. Не «кажется, бледно», а «контраст 1.36:1 при норме 4.5:1, файл `styles.css`, селектор `.today-eyebrow`, severity P0». Такую находку нельзя замылить — её можно только починить или осознанно отклонить с причиной. Так и вышло. Аудитор прошёл по токенам обеих тем — Light и Dark, — вычислил контраст для каждой ключевой пары с конкретным ratio, флагнул тач-таргеты меньше 44px, риски переполнения (тот самый `slice()`), монохромный рендер emoji и сигналинг состояния одним цветом. Каждая находка — с вычислением и severity. После сведения и дедупликации находки превратились в приоритизированный бэклог задач в GitHub под общим epic. P0 здесь — не «критично вообще», а узкий критерий: блокирует портфолио-лонч или ломает одну из двух тем. Контраст eyebrow и размер play-кнопки в него попали. Остальное — в очередь по приоритету, а не «когда-нибудь». Разница с вкусовым ревью простая: вкусовое нельзя закрыть, строгое — можно. `.today-eyebrow` ушёл с 1.36:1 на 5.46:1. Play-кнопка — с 38px на 44px. Строка серии получила минимальную высоту 58px вместо ~40px. Применено в коде, PR смержены. Не «учтём на будущее» — закрыто.

Связь дизайн ↔ код должна быть двусторонней, иначе аудит бьёт по макету

Здесь обычно ломается. Дизайн-аудит проверяет макет. Макет — картинка намерения, а не то, что крутится у пользователя. Можно вылизать Figma до пикселя и не заметить, что в `styles.css` живёт `38px`, потому что когда-то так лёг inline-стиль. Я завязал систему наоборот: источник истины — код, а не макет. Компоненты в Figma ссылаются на исходник — Telegram-набор на `apps/api/src/bot/text.ts`, Mini App на `tg/webapp/styles.css`. Токены собраны из реальных значений тем, включая замороженные нативные цвета Telegram, чтобы чат-макеты совпадали с настоящим клиентом, а не с придуманной палитрой. Поэтому аудит и нашёл баги в проде, а не в картинке. Он читал тот же `styles.css`, который отдаётся пользователю. `.today-eyebrow { color: var(--skazia-primary) }` на кремовом фоне — это свойство не макета, а продакшена. Аудитор посчитал ratio по факту, а не по тому, как «должно бы выглядеть». Обратная сторона: фикс живёт в компоненте, а не только в коде. `EpisodeRow` теперь несёт статус символом ✓/🔒 **и** текстом — не только цветом, и минимальную высоту 44px. Следующий, кто соберёт экран из этого компонента, физически не сможет повторить старый баг. Аудит не пропатчил строку — он поднял планку компонента так, что регрессия требует усилия. Вот тут проходит граница. Подправить цвет на одном экране — это оформление. Сделать так, чтобы доступный статус был свойством компонента, а контраст — свойством токена, проходящего AA в обеих темах, — это продуктовое решение. Оно переживёт меня, следующего дизайнера и следующий редизайн.

Мультиагентность ускоряет мышление ролей — не отрисовку

Теперь про то, как это было запущено, потому что тут легко обмануться. Анализ вёл мультиагентный workflow — четыре роли параллельно над одним и тем же кодом. DS-дизайнер проектировал трёхуровневую систему токенов с модами Light/Dark и считал контраст пар в обеих темах. UX-дизайнер читал состояние и копию и сверял каждое пользовательское состояние с уже нарисованными экранами — искал пробелы с триггером, файлом-источником и точным ключом копии. Аудитор a11y считал ratio, ловил тач-таргеты и сигналинг цветом. Продакт сводил всё в приоритизированный бэклог с готовыми телами GitHub-issue. Соблазн — распараллелить и саму работу. «Пусть четыре агента одновременно и рисуют в Figma». Это ошибка, и дорогая. Figma-файл — один. Запись в него — общий ресурс. Два агента, пишущие в один файл одновременно, дают не ×2 скорость, а гонку: один затирает компонент другого, `combineAsVariants` ломает сайзинг сета под чужой паддинг, `resize` сбрасывает hug-режимы — и на разбор конфликта уходит больше времени, чем сэкономлено на параллели. Та же история, что и параллельные ветки в одних файлах: помогает, пока зоны не пересекаются, и превращается в merge-ад, как только пересеклись. Поэтому правило плоское: **мультиагентность только для анализа. Запись в один Figma-файл сериализуется.** Параллелится мышление ролей — четыре взгляда на код. Отрисовка идёт последовательно, со скриншот-валидацией после каждого блока. Это не компромисс из-за слабого инструмента. Это различие между «думать» и «коммитить». Думать можно вчетвером и вразнобой — идеи не конфликтуют. Писать в общее состояние нужно по одному, иначе получишь не результат, а разбор того, кто кого перетёр.

Что отсюда забрать

Четыре реальных бага в проде Сказии — eyebrow 1.36:1, play 38px, статус одним цветом, режущий `slice()` — найдены не глазом, а аудитом со строгим критерием, и закрыты как задачи, а не как пожелания. Дизайн-ревью имеет смысл ровно настолько, насколько оно строгое. Если находку нельзя проверить, ей нельзя присвоить severity и её нельзя закрыть — это разговор о вкусе, а не работа. Привяжи аудит к коду как к источнику истины, дай каждой находке ratio, файл и критерий готовности — и он начнёт окупаться так же, как код-ревью: ловит то, что в проде, до того, как это поймает пользователь. И последнее: масштабируй мышление, а не запись. Параллель хороша там, где состояние не общее. Появился единственный файл, в который все пишут, — сериализуй. Правило одинаково работает для агентов в Figma и для людей в репозитории.