Формат: переопределения вложенных инстансов (references/instance-overrides.md)
Источник:
ds-docs/src/skills/astra-figma-to-md/references/instance-overrides.md— справочник скилла astra-figma-to-md
Как записывать то, что компонент меняет у чужого инстанса внутри своей структуры. Порядок разбора, без которого этот формат не работает, — в documentation-order.md.
Задача
Basic List Item вставляет Divider. У Divider по умолчанию ширина FIXED 190,
а внутри строки списка он стоит FILL и растянут STRETCH. Это не свойство Divider
и не свойство Basic List Item — это свойство их стыка, и живёт оно только в карточке
родителя.
Сейчас такие вещи теряются. references/document-format.md уже говорит «если документируемый
компонент что-то у вложенного переопределяет — пишется только переопределение», но не задаёт
ни списка, что проверять, ни формы записи. Из-за этого результат зависит от исполнителя,
а check-card.js не может проверить, что проверку вообще делали.
Два формата, а не один
Сборка — машинный JSON, который отдаёт Figma. Один вызов на компонент, состав полей задан жёстко. Это то, что нельзя собрать «на глаз».
Запись — то, что попадает в markdown-карточку. Короче сборки: из неё выкинуто всё, что совпадает с умолчанием, и всё, что является следствием, а не решением.
1. Сборка: что читать из Figma
get_design_context дельты не отдаёт — он рендерит инстанс уже применённым, и отличить
«здесь так решили» от «так у компонента по умолчанию» по его выводу нельзя. Дельта берётся
только через use_figma.
// Дельты прямых инстансов внутри базового варианта мастера.
// PAGE_ID и MASTER_NAME подставить из figma-navigation-mcp.md.
const p = await figma.getNodeByIdAsync(PAGE_ID);
await p.loadAsync();
const m = p.findAllWithCriteria({ types: ["COMPONENT_SET", "COMPONENT"] })
.find(n => n.name === MASTER_NAME);
const base = m.type === "COMPONENT_SET" ? m.children[0] : m;
// прямые инстансы: внутрь чужого инстанса не спускаемся — его содержимое принадлежит владельцу
const stack = [...base.children], insts = [];
while (stack.length) {
const n = stack.pop();
if (n.type === "INSTANCE") { insts.push(n); continue; }
if (n.children) stack.push(...n.children);
}
const out = [];
for (const inst of insts) {
const mc = await inst.getMainComponentAsync();
if (!mc) continue;
const owner = mc.parent && mc.parent.type === "COMPONENT_SET" ? mc.parent : mc;
const props = {};
for (const [k, v] of Object.entries(inst.componentProperties || {})) props[k.split("#")[0]] = v.value;
out.push({
layer: inst.name,
component: owner.name,
variant: mc.name,
props,
sizing: inst.layoutSizingHorizontal + "/" + inst.layoutSizingVertical,
mainSizing: mc.layoutSizingHorizontal + "/" + mc.layoutSizingVertical,
size: Math.round(inst.width) + "x" + Math.round(inst.height),
mainSize: Math.round(mc.width) + "x" + Math.round(mc.height),
align: inst.layoutAlign, grow: inst.layoutGrow,
positioning: inst.layoutPositioning,
visible: inst.visible, opacity: inst.opacity,
boundVars: Object.keys(inst.boundVariables || {}),
fields: [...new Set((inst.overrides || []).flatMap(o => o.overriddenFields))],
});
}
return out;
Ключ ко всему — instance.overrides. Figma сама помечает, какие поля разошлись с мастером,
и возвращает их именами: boundVariables, explicitVariableModes, fills, height, width,
layoutPositioning, name, characters, componentProperties, strokeTopWeight и так далее.
Это единственный источник, где «изменено» отделено от «унаследовано».
Один вызов на компонент. В бюджет чтения MCP добавляется одной строкой.
2. Словарь: что считается переопределением
Проверяется весь список, в этом порядке. Пункт, по которому расхождения нет, в запись не идёт — но проверен должен быть каждый.
| 1 | Вариант | props (оси вариантов) | Icon Button внутри Navigation Bar запиннен в Size=Small, Type=Ghost |
| 2 | Булев слот | props вида Show * | SYSTEM Status Bar внутри Navigation Bar: Show Background = false |
| 3 | Swap | props вида Swap * | ✱ Icon Container внутри Button: подставлена конкретная иконка |
| 4 | Раскладка | sizing ≠ mainSizing, align, grow | Divider внутри Basic List Item: FIXED/HUG → FILL/HUG, STRETCH |
| 5 | Поток | positioning | ⚒︎ State Layer внутри Basic List Item вынут в ABSOLUTE |
| 6 | Размер | fields: width, height, min*, max* | Checkbox внутри Basic List Item: зафиксированы min/max по обеим осям |
| 7 | Токен и режим | fields: boundVariables, explicitVariableModes, fills | Icon Button внутри Navigation Bar: переключён режим цвета под тёмную подложку |
| 8 | Рамка | fields: stroke*Weight | ✱ Icon Container внутри Button |
| 9 | Видимость | visible, opacity | — |
| 10 | Имя слоя | fields: name | Icon Button внутри Navigation Bar переименован в Icon Button Back |
Что переопределением не считается
- Текст-образец.
characters,styledTextSegments, значения строковых свойств вродеTitle = "Заголовок". Это наполнение примера, а не решение о компоненте. - Демо-иконка, если слот всё равно заменяемый снаружи. Пишется факт «слот заполнен по умолчанию», а не имя конкретной иконки.
- Следствия
FILL. Если ширина сталаFILL, тоwidth: 375— это результат, а не решение. ПишетсяFILL, число не пишется. - Расхождение размера мастера и инстанса без флага в
fields. Мастер варианта может стоять в сетке других габаритов; пока Figma не пометилаwidth/heightкак overridden, это не дельта.
3. Запись: как это выглядит в карточке
Дельта живёт в карточке родителя, в узле «Анатомии» того самого вложенного компонента — там, где он лежит в дереве. Отдельного раздела не заводится: сводная таблица переопределений оторвана от места, где они действуют, и первой же расходится с макетом.
Узел вложенного компонента несёт строго эти строки:
<details>
<summary><code>Divider</code> — отбивка строки</summary>
- **Роль:** отделяет строку от следующей
- **Свойства здесь:** `Сторона = Both` · `Толщина = 3xs` · `Отступ = Off`
- **Переопределено:** `width`: 190 FIXED → 100% FILL · `align-self`: INHERIT → STRETCH
</details>
Ключи — те же, что во всей «Анатомии»; отдельных Компонент и Вариант не заводится:
ссылка на карточку стоит в <summary> узла, а значения осей идут в Свойства здесь.
Правила обеих строк:
Свойства здесь— пропсы: оси вариантов, булевы, свапы. Проверяются все, а не только переключённые: ось в умолчании тоже называется, иначе непонятно, проверяли её или пропустили;Переопределено— то, что правлено поверх компонента: раскладка, поток, размер, токены, рамка, видимость, имя слоя. По словарю выше, сверху вниз;- формат элемента — два яруса. Значение совпало с умолчанием —
Имя = Значение. Значение изменено —Имя:умолчание→здесь. Стрелка означает ровно одно: здесь принято решение, отличное от компонента; - разделитель между элементами —
·; - имена свойств — как в CSS, теми же словами, что в остальной «Анатомии»
(
width,align-self,position,background), кроме тех, у которых CSS-эквивалента нет (Swap,constraints) — эти остаются фигмовскими; - пустота записывается словом.
свойств нет·всё по умолчанию·совпадает с умолчанием·не проверено. Отсутствие строки ине провереноозначают разное: первое читается как «забыли», второе — как честно отмеченный пробел.
Когда дельт больше четырёх, строка заменяется таблицей — иначе она не читается:
- **Свойства здесь:** `Size`: `Medium` → `Small` · `Type`: `Primary` → `Ghost`
- **Переопределено:**
| Свойство | У компонента | Здесь | Зачем |
|---|---|---|---|
| `background` | `--color-surface-primary` | `--color-surface-on-color` | шапка лежит на тёмной подложке |
| `color` | `--color-icon-primary` | `--color-icon-on-color` | то же |
| `name` | `Icon Button` | `Icon Button Back` | слой отвечает за возврат |
Колонка «Зачем» заполняется только тогда, когда причина прочитана — из аннотации автора или из очевидной механики (растяжение по ширине родителя). Догадка сюда не идёт: пустая клетка честнее выдуманной причины.
4. Когда вложенный компонент ещё не разобран
Такого быть не должно — на то и порядок. Но если карточки ребёнка нет, а родителя разбирают:
- дельту всё равно собрать и записать — значения «у компонента» берутся прямо из мастера;
- ссылку в строке
**Компонент:**поставить на секцию Figma, а не на карточку; - в
audit.mdзавести находку классаGAP: «родитель разобран раньше ребёнка».
5. Что добавить в проверку
check-card.js может стеречь формат механически:
- у каждого узла-инстанса
◆обязаны быть обе строки —Свойства здесьиПереопределено; - значение — либо служебное слово из закрытого списка (
свойств нет,всё по умолчанию,совпадает с умолчанием,не проверено), либо перечень элементов через·; - элемент имеет вид
Имя = Значение(умолчание) илиИмя:было→стало(решение); - имена свойств — из того же словаря, что и остальная «Анатомия»;
- токены в дельте существуют в
foundations/tokens/*.json— та же проверка, что и везде.
Включать проверку сразу нельзя. На 27.08.2026 в базе 583 узла-инстанса: у 133 нет ни одной из строк, ни один не записан ярусами. Проверка ставится после переразбора, иначе она заблокирует отправку всей документации.
Оговорки
- Формат покрывает прямые инстансы внутри мастера. Что переопределено на два уровня вглубь — забота владельца того уровня; иначе карточки начнут дублировать друг друга и разъезжаться.
- Дельты снимаются с базового варианта. Если варианты родителя переопределяют ребёнка по-разному, различие идёт в раздел «Варианты» — там, где уже описываются отличия вариантов.
- Все примеры в этом файле прочитаны из макета «Astra DS (Copy)», а не придуманы.