MCP test: find_nodes searches currentPage only — switch to
FOUNDATIONS page where Button components live.
eval-cli test: material3.fig first page has 1 TEXT node, not 3 —
assert >0 instead of ==3.
Figma distributes leading equally above and below text. CanvasKit
defaults to ascent-biased leading. halfLeading: true fixes this but
must only apply to rendering (drawParagraph), not measureTextNode —
otherwise measured sizes change and break grid layouts.
loadFontsForNodes only loaded the Regular style for each family.
Now collects fontWeight and italic from each text node and style
run, converts to style name via weightToStyle, and loads each
variant separately (e.g. Medium, Bold, Bold Italic).
btoa() only handles Latin-1. Use TextEncoder + binaryToBase64 for
encoding and TextDecoder + base64ToBinary for decoding the internal
OpenPencil clipboard format.
Figma's lineHeight with units=PERCENT stores e.g. 100 meaning 100%
of fontSize. We stored the raw value (100) which the renderer
interpreted as 100px, producing ~6x line height. Now properly
converts: PERCENT → value/100 * fontSize, PIXELS → value as-is.
Tauri's loadFont registered fonts with the CanvasKit provider but
not in core's loadedFamilies cache, so isFontLoaded returned false
and measureTextNode returned null during layout recomputation.
Add markFontLoaded() to core and call it from Tauri font loader.
Add tests verifying text measurement integrates with auto-layout
centering.
Text nodes with textAutoResize=WIDTH_AND_HEIGHT now use an estimated
content width instead of the node's current width when computing
auto-layout. Fixes month headers (and similar centered text in
auto-layout) appearing left-aligned after paste.
Replace all 'Inter' string literals with DEFAULT_FONT_FAMILY from
constants. Add loadFontsForNodes() that collects font families from
text nodes and loads them into CanvasKit, called after clipboard
paste and .fig file import.
Figma's parentIndex.position strings represent z-order, not visual
layout order. In auto-layout frames, children must be sorted by their
x (horizontal) or y (vertical) coordinate to match the visual order.
Fixes scrambled calendar dates and other auto-layout child ordering.
Each instance in Figma's clipboard carries symbolData.symbolOverrides
with per-child property changes (text, fills, visibility). The override
targets a child via guidPath which matches the overrideKey field on the
component's child node.
After cloning component children into instances, overrides are resolved
through the overrideKey→figmaId→internalId→clonedChild chain and applied
to the instance's children.
FRAME nodes with VARIANT componentPropDefs are now promoted to
COMPONENT_SET on import (both .fig files and clipboard paste).
Clipboard paste now respects the Internal Only Canvas: components
on it are used to populate instance children but are not pasted
as visible nodes, matching Figma's behavior.
When pasting from Figma, INSTANCE nodes reference their component via
symbolData.symbolID but have no children in the clipboard. If the
component was also pasted, clone its children into the instance using
the componentId mapping (cloneChildrenWithMapping).
Figma vectors use a normalized coordinate space (vectorData.normalizedSize)
that can differ from the node's actual size. The decoded path vertices and
tangents must be scaled to fit the node bounds, otherwise vectors render
at the wrong size (e.g. 16×16 path in a 12×12 node).
Extract shared kiwi→SceneNode conversion into kiwi-convert.ts, used by
both fig-import.ts (.fig file opening) and clipboard.ts (Figma paste).
The clipboard import was a simplified copy that diverged over time:
- Missing SYMBOL→COMPONENT type mapping (Figma's kiwi name for components)
- textAutoResize hardcoded to NONE instead of using clipboard value
- No gradient/image fill support, no effects, no style runs
- Missing flipX/flipY, constraints, text decorations, arc data, etc.
- Percent letter spacing not converted to pixels
Now both paths use nodeChangeToProps() with identical conversion logic.
Decode internalOnly from kiwi field 142, filter internal pages
in SceneGraph.getPages() by default (pass true to include them).
Pages with names of only dashes/asterisks/spaces and no children
render as horizontal divider lines.