astra-component-contract — Контракт компонента (YAML, нейтральный словарь)

Источник: ds-docs/component-contracts/skills/astra-component-contract/SKILL.md Справочники скилла — вложенные страницы. В вики не выложены, лежат в папке скилла: scripts/contract.mjs (валидатор, 100 КБ), scripts/figma-read.js. Команды npm run parse, tools/*.mjs — из корня ds-docs/component-contracts/.

Шапка скилла:

name: astra-component-contract
description: Читает компонент Astra DS из Figma и пишет его контракт — файл components/<slug>.yaml с осями, мастерами, анатомией, дельтами вариантов, привязками токенов, формами семейства и проброшенными наружу инстансами. Ключи английские, словарь раскладки нейтральный к платформе (не CSS). Использовать, когда просят «опиши компонент контрактом», «собери YAML по компоненту», «спарси компонент из Figma», «добавь компонент в базу контрактов».

Один компонент — один файл components/<slug>.yaml. Прозы в нём нет: контракт описывает то, что нарисовано, в машинном виде, чтобы это мог проверить скрипт и прочитать генератор кода под любую платформу.

Источник контракта — оригинал дизайн-системы, файл eGpqY0OqVR8l8zhmlHdfb9, а не рабочая копия. Копия отстаёт: сверка семейства Navigation Bar показала два значка, переставленных в оригинале и не переставленных в копии, — 140 вхождений. Копия нужна только там, где надо писать в макет, то есть для кругового прогона.

Если у компонента есть карточка в reference/ того же репозитория, она второй голос: где проза расходится с макетом, это отчёт человеку, а не повод править контракт под прозу.

Полнота контракта измеряется обратным ходом. Контракт полон, если по нему собрать компонент заново и он совпадёт с оригиналом. Этим занят скилл astra-contract-to-figma; протокол прогонов — в round-trip.md.

Порядок

Сначала скрипт, потом слова. Механическая часть — чтение, черновик, дыры из фактов, проверки — делается одной командой и агента не требует:

FIGMA_TOKEN=… npm run parse -- --id <id набора> --slug <slug>

Она пишет components/<slug>.yaml, который уже сходится с макетом в ноль. Шаги ниже описывают, что к этому добавляет разбор: смысл, сжатие повторов и дыры, которые видно только глазом. Если контракт пишется руками без скрипта — шаги те же.

  1. Что парсить и в каком порядке. Снять карту ярусов:

    FIGMA_TOKEN=… node tools/tier-map.mjs <id узла> --out maps/<имя>.json
    

    Она даёт замыкание — что во что вложено — и очередь: описывать снизу вверх, от атомов к организмам, иначе ссылку на ребёнка не с чем сверить.

  2. Откуда читать. По умолчанию — REST по оригиналу:

    FIGMA_TOKEN=… node tools/read-component.mjs <id набора> --out dump.json
    

    Дамп сразу годится для --check-against. REST не тратит квоту MCP, читает оригинал и отдаёт всё, что нужно контракту: проброс наружу, привязки переменных, свойства инстансов, имя текстового стиля. Выгрузка плагина — второй путь, MCP — только для записи в макет.

  3. Если читать всё же через MCP — загрузить скилл figma-use, без него use_figma не вызывать.

  4. Прочитать формат контракта и словарь раскладки.

  5. Разведка по чек-листу: что за узел по ссылке, оси и свойства, имена всех мастеров, деревья, проброшенные инстансы, окружение файла. Фрагменты запросов — в scripts/figma-read.js. Не больше 6–8 мастеров за вызов, у семейства — чтение диффом. Выгрузку сохранить: фрагмент 7 отдаёт её в виде, который ест --check-against, вместе с картой переменных по id. Без неё сухая сверка на шаге 12 сравнивать будет нечего.

  6. Из имён мастеров вывести counts и rules. Проверить арифметикой: правила должны давать ровно count. Не сходится — искать дальше, а не писать «примерно».

  7. Решить, сколько в семействе форм: одно дерево или несколько. Признак — нужен ли replace целого поддерева. Несколько — anatomy и variants уходят внутрь forms. Тем же признаком решается ось Type Scale (Base / Scale): вид под крупную типографику остаётся дельтой, пока дерево одно, и становится формой, когда переверстан — см. масштаб типографики.

  8. Базовое дерево перевести в anatomy по словарю. Имя переменной — последний сегмент после /. Свойство без привязки — числом.

    Механическую часть — оси, свойства, решётку, базовое дерево и дельты — снимает черновик:

    node tools/draft-contract.mjs dump.json --slug <slug> [--page "<страница>"] [--form-axis Type]
    

    Он сходится с выгрузкой в ноль расхождений сразу. Остаётся человеческое: вводный комментарий, осмысленные имена узлов, таблицы tokens с привязками вместо повторяющихся дельт и раздел gaps. Черновик — не контракт: без разбора он описывает, но не объясняет.

  9. Отличия остальных мастеров записать в variants дельтами. Значение, зависящее от оси, — таблицей в tokens и привязкой $имя.

  10. Узлы-инстансы: of — slug контракта ребёнка; props — свойства инстанса; overrides — правки поверх; exposed — проброшен ли наружу. Инстанс обёртки текста («Text / …») контрактом не описывается: читалка сворачивает его в узел kind: text с foundation: typography, а настройки — стиль, цвет, число строк, обрезку — кладёт в text. Семейство описано в фундаменте обёрток. Прочитать не удалось — null и запись в gaps. Если ребёнок уже описан, его свойства и значения сверяются с его контрактом: расхождение — ошибка. Поэтому описывать имеет смысл снизу вверх, от атомов к организмам. Ребёнок из подключённой библиотеки читается из её файла, а не из потребителя: в файле-потребителе у него пустое поддерево. Ключ библиотеки узнаётся через GET /v1/components/<key> — поле file_key; значки Astra DS лежат в IPwMyKXhxPhaJIMClmXM19 («Astra DS: Icons»). В source пишется файл библиотеки и её id, а не тот, под которым набор виден потребителю.

  11. Слой состояния — обойма State layer … либо инстанс ⚒︎ State Layer — помечается foundation: state-layer. Абсолютный инстанс задаёт область нажатия родителя; его исчезновение в каком-то мастере значит, что там не нажимается. Правила записи — формат, устройство — фундамент.

  12. SYSTEM-слои контрактов не получают. Приставка SYSTEM значит «здесь встанет элемент платформы» — строка статуса, клавиатура, полоса жеста, рамка устройства. В чужом дереве такой узел пишется of: null, local: true, foundation: system-chrome; отдельный контракт на него не заводится. Почему — фундамент.

  13. Раздел Deprication Version не разбирается. Страницы ❌ List, ❌ Sub-list, ❌ Format Container / Text State — склад отменённого: у каждого набора там в имени написана живая замена. Контрактов им не пишут. Если отменённый набор всё ещё лежит внутри живого компонента, узел записывается of: null, local: true, а в gaps — чем его заменить. То же и с системными страницами (Activity View, Device, Keyboard, Scroll Bar): их рисует платформа, см. шаг 12.

  14. Версия внутри компонента: описываем только живую. У части наборов есть ось Version (в файле встречается и Vesrion) со значениями 🟢 Stable и ❌ Deprecated (а также Depricated, Description, Stabled — пишут по-разному). Обе версии держат в одном наборе нарочно: продукт переходит на новую сменой значения свойства, а не заменой компонента, — при свапе содержимое слетает. В контракт идёт только живая ветка: у оси остаётся одно значение, счётчики считают живых мастеров, правила про отменённых не пишутся, а в gaps — строка, сколько мастеров осталось за кадром. Черновик и сухая сверка отменённых мастеров уже не берут. Ось или свойство с крестом в имени (❌ Progress) остаются: без них живого мастера не собрать — валидатор о них предупреждает. Подробно — фундамент версий.

  15. Размер экрана — от платформы, а не от компонента. Локальная коллекция режимов (Apple, Android, Bottom Nav Bar, Web, Internet Bank View, WebView) подставляет в макет размер устройства и отступы под системные полосы — это вариации показа, а не устройство компонента. Такой узел помечается foundation: platform, а размер пишется словом: size: {width: platform, height: platform}. Пределы minHeight/maxHeight, равные высоте экрана, не пишутся вовсе. Черновик делает это сам, валидатор предупреждает, если число экрана осталось. Подробно — фундамент платформы.

  16. Костыли обрезки хвоста — ⚒︎ Text Overflow Blur/Layer-01…03 и ⚒︎ Elilpses Loading/1…4 со страницы Text. Внутри обёртки они не видны: обёртка сворачивается в текстовый узел с text.truncate. Положенные в дерево руками — пишутся of: null, local: true, foundation: typography, без своего контракта. Устройство — фундамент обёрток.

  17. Всё странное или отсутствующее — в gaps классом SUSPECT или GAP.

  18. Записать файл components/<slug>.yaml. Имя файла равно полю slug.

  19. Прогнать проверку:

    node skills/astra-component-contract/scripts/contract.mjs components/<slug>.yaml --grid
    
  20. Сухая сверка — сохранить прочитанные деревья выгрузкой и прогнать --check-against. Она сравнивает развёрнутый контракт с тем, что прочитано из Figma, и не требует ни одной записи в макет. Расхождения — ошибки.

  21. Проверить новизну — --novelty. Если все конструкции контракта уже обкатаны круговым прогоном, прогон не нужен. Если появилась новая — нужен.

  22. У семейства проверить срезы: каждая форма обязана резаться в самостоятельный контракт. --out кладёт по срезу на форму рядом с целым контрактом.

  23. Показать, что получилось, и остановиться. Если у компонента есть карточка в reference/ — перечислить расхождения отдельным списком: это находки для аудита, а не правки. В GitLab и на сервер ничего не выгружать.

Что скрипт умеет

contract.mjs <контракт.yaml>разбирает YAML, проверяет
contract.mjs <карточка.md>достаёт машинный блок за маркером из карточки reference/
--docs <корень>откуда брать выгрузку токенов и контракты детей
--out <папка>выгрузить в JSON — для генераторов кода и для рендера; у семейства ещё и срезы
--gridвывести решётку мастеров и наборов, сверить с count, sets и counts
--form <id>напечатать срез одной формы семейства — самостоятельный контракт
--resolve "<мастер>"развернуть один мастер в полное дерево
--check-against <выгрузка.json>сухая сверка: развернуть контракт и сравнить с деревом, прочитанным из Figma
--noveltyпоказать конструкции, которые ещё не обкатаны круговым прогоном

Что формат умеет

дельты set · add · remove · swap · replace · moveварианты как отличия от базы, а не копии
привязки $имя по осиодно значение на ось вместо повторов в каждом правиле
slots и swapFromподмена вложенного компонента через свойство набора
exposedпроброс свойств вложенного компонента наружу — публичный API
text.style, overrides.textStyleтипографика только именем стиля файла; токены стиля — дело фундамента
axes[].in: nameсемейство: сегменты имени набора как измерения
formsсемейство из нескольких структурно разных деревьев под общей шапкой
hiddenузлы, скрытые по умолчанию
gapsвсё, чего в макете нет или что в нём странно

Проверка круговым прогоном

Что делать с непоследовательностью макета — выбор автора контракта. Записать её в gaps и жить с расхождением, либо выразить узким правилом when и получить точное совпадение. Второе длиннее: у Tag на четыре узла базового дерева пришлось 19 правил, из них семь — исключения на один-два мастера, зато прогон сошёлся до нуля. Это про августовскую редакцию контракта: после перестройки по оригиналу прогон 23.09.2026 дал 467. Ноль привязан к версии контракта, а не к компоненту, и после перестройки его надо получать заново.

Второй вариант словаря — на будущее

Если решим, что CSS понятнее разработчикам: меняется только layout-vocabulary.md и таблица имён в скрипте. Структура контракта, операции, привязки, чтение Figma и проверки остаются.

Что уже описано

Контракты лежат в components/ в корне репозитория.

button5 осей, 273 мастера, подмена на индикатор, новая ветка слоёв, слой виброотклика в interaction, 21 находка
tagдва слота подмены, замена подписи на маску и на скелетон, текстовые стили файла; прогон 23.09.2026 — 467, разбор в round-trip.md
navigation-barсемейство: 19 наборов, 76 мастеров, восемь форм под общей шапкой, 24 находки