Figmas Node-Baum ist reich, aber laut. Frame, Group, Component, Instance, Text, Rectangle, Ellipse, Vector — jeder hat Dutzende optionaler Felder, von denen einige miteinander in Konflikt stehen. Ein Agent, der direkt auf der rohen Figma-API-Antwort arbeitet, verbraucht kognitive Kapazität damit, das Schema zu verstehen, anstatt Code zu schreiben.
figmascope normalisiert den Baum in vier Node-Arten: stack, overlay, absolute und leaf. Jeder Node in jeder screens/*.json-Datei ist eine dieser vier. Nichts anderes.
Die vier Arten
stack
Ein Stack ist ein Node mit Figma Auto Layout — layoutMode: "VERTICAL" oder layoutMode: "HORIZONTAL". Seine Kinder sind entlang der Achse fließend positioniert. Abstände und Padding sind explizit.
{
"kind": "stack",
"id": "123:456",
"name": "ContentColumn",
"axis": "vertical",
"gap": 16,
"paddingTop": 24,
"paddingBottom": 24,
"paddingLeft": 20,
"paddingRight": 20,
"primaryAxisAlignItems": "SPACE_BETWEEN",
"counterAxisAlignItems": "CENTER",
"width": 390,
"height": 844,
"fills": [{ "type": "SOLID", "color": "#1a1a2e" }],
"children": [ ... ]
}
Mappt auf Jetpack Compose als eine Column (vertikale Achse) oder Row (horizontale Achse). Die Felder primaryAxisAlignItems und counterAxisAlignItems mappen auf Arrangement bzw. Alignment. gap wird zu Arrangement.spacedBy().
overlay
Ein Overlay ist ein Node, dessen Kinder absolute Positionen innerhalb von ihm haben. Figma repräsentiert das als Frame mit layoutMode: "NONE", wo Kinder absoluteBoundingBox-Koordinaten haben. Die Overlay-Art erfasst das, ohne dass der Agent über rohe Koordinaten nachdenken muss, um die Struktur zu verstehen.
{
"kind": "overlay",
"id": "123:789",
"name": "CardOverlay",
"width": 358,
"height": 200,
"fills": [{ "type": "SOLID", "color": "#ffffff" }],
"children": [
{
"kind": "absolute",
"offset": { "x": 16, "y": 16 },
"child": { ... }
}
]
}
Mappt auf Compose als Box. Kinder werden mit Modifier.offset() oder Modifier.align() positioniert, abhängig davon, wie ihre Positionen mit den Elterngrenzen zusammenhängen.
absolute
Ein absoluter Node ist ein Wrapper, der ein einzelnes Kind an einem bestimmten (x, y)-Offset innerhalb seines übergeordneten Overlays trägt. Es ist ein dünner struktureller Node — er existiert, um die räumliche Beziehung aus Figma zu erhalten, ohne Position mit Inhalt zu vermischen.
{
"kind": "absolute",
"offset": { "x": 24, "y": 140 },
"child": {
"kind": "leaf",
"name": "BadgeLabel",
"text": "NEU",
"stringRef": "badge.new",
...
}
}
In Compose wird das typischerweise zu einem Kind einer Box mit Modifier.offset(x.dp, y.dp). Die Offset-Werte sind Kandidaten für Token-Substitution, wenn Spacing-Tokens im Bereich existieren.
leaf
Ein Leaf ist ein Terminal-Node — keine Kinder. Er hat Styling (Fills, Strokes, Corner Radius) und optional Text-Inhalt. Text-Leaves haben ein text-Feld (der Literal-String) und ein stringRef-Feld (der i18n-Ressourcen-Key aus strings.json).
{
"kind": "leaf",
"id": "123:101",
"name": "SpeedLabel",
"text": "Geschwindigkeitstest",
"stringRef": "speed.test",
"fontSize": 18,
"fontWeight": 600,
"fills": [{ "type": "SOLID", "color": "#ffffff" }],
"width": 160,
"height": 28
}
Mappt auf Compose als Text()-Composable für Text-Inhalt, oder als Surface, Box oder Image für Nicht-Text-Leaves abhängig vom Fill-Typ.
Die vier Arten bilden eins-zu-eins auf das kompositionelle Modell jedes UI-Frameworks ab. Stacks sind Flow-Layouts. Overlays sind z-positionierte Container. Absolutes tragen räumliche Metadaten. Leaves sind Inhalt. Das ist alles. Die Figma-Node-Taxonomie hat 12+ Node-Typen — das IR reduziert es auf vier.
Wie die Art bestimmt wird
Die Klassifizierungslogik:
- Wenn
layoutMode"VERTICAL"oder"HORIZONTAL"ist →stack - Wenn
layoutMode"NONE"ist und Node Kinder hat →overlay(Kinder alsabsolutegewickelt) - Wenn ein Node das direkte Kind eines Overlays ist und eine Position trägt →
absolute - Wenn ein Node keine Kinder hat und kein absoluter Wrapper ist →
leaf
Die layout-mode-none-inferred-Warnung in _meta.json feuert, wenn ein Frame mit layoutMode: "NONE" Kinder mit nicht-trivial überlappenden Bounding Boxes hat. figmascope behandelt ihn als Overlay, vermerkt aber die Ableitung, weil manche layoutMode: "NONE"-Frames in der Praxis einfach Container für ein einzelnes Kind sind (keine Überlappung) und vereinfacht werden könnten. Die Warnung lässt den Agenten entscheiden, wie er damit umgeht.
absoluteBoundingBox — warum er erhalten wird
Jeder Node im IR behält seine absoluteBoundingBox aus Figma, selbst wenn der Elternteil ein Stack ist (wo absolute Positionen theoretisch für das Layout irrelevant sind):
{
"kind": "stack",
"absoluteBoundingBox": { "x": 0, "y": 88, "width": 390, "height": 756 },
...
}
Das ist für Agenten da, die räumliche Beziehungen zwischen Nodes nachdenken müssen, die keinen direkten Eltern-Kind-Bezug teilen. Das Überlappen zweier Elemente aus verschiedenen Ästen des Baums erfordert das Kennen ihrer absoluten Positionen, nicht nur ihrer relativen Offsets. Das Koordinatensystem ist Figmas Canvas — Ursprung oben links, y nach unten zunehmend.
Es hilft auch beim PNG-Querverweise. Die _meta.json enthält ein pngCount-Feld und die exportierten PNGs sind nach Screen-Slug benannt. Wenn man das IR mit dem Screenshot vergleicht, ermöglicht absoluteBoundingBox das Lokalisieren spezifischer Nodes innerhalb des Bildes. Um das am eigenen Design zu sehen, ein Bundle von figmascope.dev exportieren.
Fills und Strokes auf Container-Arten
Ein häufiger Verwechslungspunkt: Stack- und Overlay-Nodes können Fills haben, nicht nur Leaves. Eine Column mit Hintergrundfarbe ist immer noch ein Stack — sie hat einfach auch ein fills-Array. figmascope bewahrt das:
{
"kind": "stack",
"axis": "vertical",
"fills": [{ "type": "SOLID", "color": "#f6f2ea" }],
"cornerRadius": 12,
...
}
In Compose wird das typischerweise zu einer Column innerhalb einer Surface oder Card, mit der Fill-Farbe und dem Corner Radius auf den Container angewendet. Das IR kollabiert den Fill nicht in die Kinder — es behält ihn auf dem Node, wo Figma ihn gesetzt hat.
Verlaufs-Fills auf Container-Arten emittieren eine background-gradient-not-supported:<name>-Warnung. Der Node ist immer noch im IR mit seinen anderen Feldern intakt präsent. Der Agent sollte den Fill als TODO behandeln und entweder mit einer Volltonfarbe approximieren oder einen benutzerdefinierten Zeichenaufruf generieren, gemäß den Scope-Hinweisen in CONTEXT.md.
Die stringRef + text-Beziehung
Text-Leaf-Nodes tragen beides:
text: der Literal-String-Wert aus Figma (z.B."Geschwindigkeitstest")stringRef: der Ressourcen-Key ausstrings.json(z.B."speed.test")
Wenn der Inhalt eines Text-Nodes aus strings.json wegen einer Kollision oder eines Filters herausgefallen ist (nur Zahlen, leer, zu kurz), hat der Leaf immer noch text, aber stringRef fehlt. Der Agent sollte in diesem Fall auf den Literal zurückfallen. Siehe strings.json für die vollständige Kollisionsbehandlungslogik.
Wenn beides vorhanden ist, sagt die CONTEXT.md-Einschränkung, stringRef zu verwenden. Das text-Feld ist für das Agentendenken da (damit er weiß, was der String sagt, ohne strings.json zu öffnen) und als Fallback.
Komponenteninstanzen im IR
Wenn ein Figma-Node eine INSTANCE einer Komponente ist, trägt der IR-Node zwei zusätzliche Felder:
{
"kind": "stack",
"componentId": "789:012",
"componentName": "PrimaryButton",
...
}
Das verlinkt zurück auf components/inventory.json. Der Agent weiß, dass dieser Node eine Instanz von PrimaryButton ist, anstatt ein eigenständiges Layout, und sollte die bestehende Komponente referenzieren, anstatt duplizierten Code zu generieren. Die vollständige Abdeckung davon ist in Komponenten-Inventar.
Echtes Screen-Beispiel
Ein vereinfachtes, aber strukturell genaues Beispiel eines Screens mit einem Header, einer Inhaltsspalte und einem schwebenden Button:
{
"screen": "home",
"root": {
"kind": "overlay",
"name": "HomeScreen",
"width": 390,
"height": 844,
"fills": [{ "type": "SOLID", "color": "#0d0d1a" }],
"children": [
{
"kind": "absolute",
"offset": { "x": 0, "y": 0 },
"child": {
"kind": "stack",
"name": "ContentArea",
"axis": "vertical",
"gap": 24,
"paddingTop": 56,
"paddingLeft": 20,
"paddingRight": 20,
"children": [
{
"kind": "leaf",
"name": "Title",
"text": "Geschwindigkeitstest",
"stringRef": "speed.test",
"fontSize": 28,
"fontWeight": 700
}
]
}
},
{
"kind": "absolute",
"offset": { "x": 111, "y": 752 },
"child": {
"kind": "stack",
"name": "StartButton",
"componentId": "321:654",
"componentName": "PrimaryButton",
"axis": "horizontal",
"gap": 8,
"paddingTop": 16,
"paddingBottom": 16,
"paddingLeft": 32,
"paddingRight": 32,
"cornerRadius": 24,
"fills": [{ "type": "SOLID", "color": "#7f5cfe" }]
}
}
]
}
}
Daraus kann der Agent korrekt einen Compose-Box-Screen mit einer absolut positionierten Column für Inhalt und einem absolut positionierten PrimaryButton-Komponent unten generieren. Jede Layout-Entscheidung ist aus dem IR ableitbar, ohne zu raten.
Wie Tokens auf die Abstands- und Farbwerte in dieser Struktur angewendet werden, ist in tokens.json erklärt beschrieben. Für den vollständigen Agenten-Workflow, der dieses IR mit Cursor oder Claude Code verwendet, siehe Jetpack Compose aus Figma. Selbst ausprobieren durch Einfügen der Figma-URL in figmascope.