Spec reference
Shape parts
Parametric vector decor — triangles, ellipses, rotated rects — carried as geometry, projected as clip-paths in code and real vector nodes on the canvas.
Shape partsGeneratedCurated#
A leaf decor part that is a parametric vector, not a box — the projection of capture-geometry into the contract (field case: a tooltip’s pointer triangle). Bounded by construction: exactly three kinds — polygon by side count, ellipse, rect — an explicit intrinsic size, and a CSS-clockwise rotation. Everything else about a shape rides existing channels: fill via tokens.background-color, per-variant placement via stylesWhen, visibility via visibleWhen.
polygon | ellipse | rect.
Polygon point count, ≥ 3. A polygon with no captured side count renders the canvas default (3) — and the proposer names that assumption in its notes.
Intrinsic (pre-rotation) width, px.
Intrinsic (pre-rotation) height, px.
CSS-clockwise degrees. Omit for 0.
Ellipse-only partial sweep — see ellipse arcs.
Projections: code surfaces render width/height + clip-path: polygon(…) (or border-radius: 50%) + transform: rotate(…) — one shared implementation (shapeCssDecls in the schema module) so the projection cannot fork across emitters; the canvas generator constructs a real RegularPolygon/Ellipse/Rectangle node with native rotation.
Refusals: Each of these fails the build by name — the generator refuses, it never papers over (source: core/emit-react.ts validateContract, exercised by the C2 eval family).
- a shape part must be a leaf — no
parts,slot,component,content,text,icon, ormeteralongside it sidesonly on polygons — side count is polygon vocabulary
{
"Pointer": {
"shape": {
"kind": "polygon",
"sides": 3,
"width": 12,
"height": 12
},
"tokens": {
"background-color": "{imported.tooltip.pointer.background-color}"
},
"visibleWhen": {
"prop": "pointer"
},
"stylesWhen": [
{
"prop": "pointerPosition",
"equals": "topRight",
"styles": {
"position": "absolute",
"right": "12px",
"top": "-8px"
}
},
{
"prop": "pointerPosition",
"equals": "none",
"styles": {
"display": "none"
}
},
{
"prop": "pointerPosition",
"equals": "bottomRight",
"styles": {
"position": "absolute",
"right": "12px",
"bottom": "-8px",
"transform": "rotate(180deg)"
}
}
]
}
}Every value above — the 12×12 intrinsic size, each inset, each rotation — comes from the captured file, not from this page. The standing receipt for this field case is npm run extract:figma:tooltip:check.
Ellipse arcsGeneratedCurated#
shape.arc — partial-sweep geometry for an ellipse shape (a spinner’s three-quarter ring, a donut gauge), carried as Figma Plugin-API ArcData radians, verbatim: 0 at 3 o’clock, increasing clockwise on screen. Ellipse-only vocabulary, and carried only when the sweep is partial (< 2π) and constant across variants — a full sweep is the plain ellipse, and an axis-varying sweep rides per-value stylesWhen mask rules instead (the rotation discipline).
Sweep start, radians — 0 at 3 o’clock, increasing clockwise on screen.
Sweep end, same convention.
Figma’s donut-hole fraction, 0–1 — recorded for round-trip fidelity, see below.
Projections: code surfaces render the sweep as a conic-gradient mask over the part’s border-drawn ring — one spelling, arcMaskCss in the schema module, shared by every emitter so the projection cannot fork; the canvas generator sets native arcData (core/emit-figma-script.ts).
Refusals: Each of these fails the build by name — the generator refuses, it never papers over (source: core/emit-react.ts validateContract, exercised by the C2 eval family).
arcon a non-ellipse shape — sweep is ellipse vocabulary
Honest carriage: the mask’s hard color stops render butt caps — Figma’s stroke-cap style is not on the dump surface, a named residue. innerRadius at 1 (the observed class — a pure stroked ring) costs nothing: the border-drawn ring already leaves the hole. The proposer names innerRadius < 1 (a filled donut) instead of carrying it.
{
"shape": {
"kind": "ellipse",
"width": 16,
"height": 16,
"arc": {
"start": -1.5708,
"end": 3.1416,
"innerRadius": 1
}
},
"tokens": {
"border-color": "{color.icon.brand}"
}
}