In Figma's data a component set holds one text property per variant (plus leftovers of deleted variants), and an instance lists only the ones its current variant's layers read (measured in Figma's web app). FigCraft listed every definition on every instance, and wrapped a long property name inside an 84 x 20 box so its lines painted over the rows around it.
- components::instance_prop_list: prop_list keeping slots and the properties a layer of the component references (a nested instance counts, its layers and slot content are not entered). component.properties and the Properties panel use it for instances; sets, components and variants still list every definition.
- prop_label cuts the name to one line with an ellipsis and shows the full name on hover.
- Tests: an_instance_lists_only_the_properties_its_variant_uses, a_long_property_name_stays_on_one_line, and the pin a_set_keeps_each_named_text_definition_and_layers_read_their_own. Two tests and one setup that listed an unbound boolean on an instance now bind it to a layer.
- docs/components-parity.md: the properties row, header and revision history.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.1 briefed; Sonnet 5.5 wrote at medium
From the pre-ready review: a sticky or shape item on a non-frame layer is ignored with the warning; the author drops control characters as board.sticky does; dump_samples writes a whiteboard's board items; a doc link made plain text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote; Fable 5.1 reviewed
On a FigJam whiteboard the mapper builds a sticky with complete generated records as a native board sticky: the saved colour, size, place, opacity and shadows, the note and the author line as text layers, no background vector. It writes a board field on the REST JSON, which the REST importer reads (junk values are ignored with a warning). Damaged stickies and stickies in a .fig keep the generated layers.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.1 briefed; Sonnet 5.5 wrote at medium
Figma holds a US registration for DEV MODE (US 7574348). Display text only: the view.devMode label, Inspect tab, shortcut row, refusal messages, CLI help, docs and all locale catalogs now say Developer Mode. Command ids, preference keys and saved fields are unchanged; "Dev Mode" stays a search alias in the Actions palette. Adds a naming guard test.
Figma rounds every point whose style doesn't set a corner radius with the
layer's cornerRadius. The import read only the point's style, so those
vectors came in sharp. Points now fall back to the layer's radius, at the
scale of the layer that carries it (#2091), and the network cache keys on
it. The export writes an explicit 0 for a sharp point on a rounded vector
so it stays sharp.
The scaled-instance round-trip test now asserts the scaled width and height instead of printing them. text_path read the layer's raw size, so a text on a path inside a scaled instance kept the main's path length; it now uses the scaled or fitted size.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
- A nested instance's override and derived rows (and its text property values) are written in the enclosing main's space: OverrideRow carries the factor of the layer that supplied it and View::with takes (row, scale) pairs. Test: a_nested_instances_own_rows_are_at_its_enclosing_mains_scale.
- Progressive blur startOffset and endOffset are unit-box fractions, not lengths: only offset and noiseSize scale. Test: a_progressive_blurs_positions_are_not_lengths.
- An effect or grid style takes the factor of the layer that links it (1 when an instance row swaps it), not the main's. Test: an_effect_style_takes_the_scale_of_the_layer_that_links_it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.1 reviewed; Sonnet 5.5 wrote at high
An instance scaled with the Scale tool in Figma stores `symbolData.uniformScaleFactor`; the importer never read it, so the layers of a 128 px instance of a 256 px component came in at 256 px with the main's radii, strokes and spacing. Layers of an instance now take the main's lengths times the factor (composed through nested instances), the same values the Scale tool scales, unless an override or derived row gives the value, which is already at the instance's scale. Eight tests: the reproducer, the round trip, a derived size row not scaled twice, override rows, auto layout gap/padding/text, nested composition, factor 1 or none, and junk factors ignored.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 wrote at high
* M3: glass effects take variables, and their frost stays within 0–100 (#1885)
Glass's light angle, intensity, refraction, depth, dispersion, frost and
splay bind to number variables, on a layer (`variable.bind`,
`effects/<i>/<field>`, `radius` being the frost) and in an effect style
(`style.bind`), and the Design panel's glass fields take a variable like
the shadow and blur fields. A variable reads as the field shows it:
intensity, refraction, dispersion and splay in percent.
A glass's frost stays within 0–100, the reference tool's range: a larger
frost from an older file draws at 100, and the field, a bound variable and
scaling stop there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: a frost from an older file scales and shows as it draws (#1885)
Review follow-up: a frost saved past 100 draws at 100, so scaling a layer
starts from that (150 halved draws at 50, not 75) and the Design panel's
Frost field shows it. The revision row's binding path escapes its pipes, so
the table keeps three columns.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: Dev Mode and .fig export show a frost from an older file as it draws (#1885)
A frost saved before the 0–100 range draws at 100, and the canvas, CSS and
scaling already read it that way, but Dev Mode's Glass row and the .fig
export still read the stored number, so they said 150 where the canvas
shows 100. Both now read the sanitized frost.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Oleksii Sukholita <oleksii.sukholita@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
From review: `text-align: left` beside `direction: rtl` (Left is absolute on the canvas); the REST import reads the four parallel paragraph arrays up to the paragraph count, not the 256-entry list limit; `rtl_para` gets an entry for the position after the last char so an empty last paragraph keeps its forced direction; the localization status line.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Direction came from the letters only; Add right-to-left text says controls override it per paragraph (Text > Text direction, Left to right or Right to left). `TextDirection` on `ParaList`, `Bidi::of` with a base level per paragraph, `text.direction` and the three menu commands (the edited paragraphs while editing), `node.set textDirection`, the Type settings control once a right-to-left script is in the text, inspect `textDirection` / `lineDirections`, the CSS `direction`, and the `.fig` lines' `sourceDirectionality` in and out.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Review: the first and last runs were checked for lying at right angles to their sides, not for
going out of the object, so a path whose first run went up through the start's box (or whose last
arrived from inside the end's) was kept and drawn through it. Both runs now have to go the way the
side faces; one along the side or back into the object leaves the connector to the router. Two more
cases in the routed-here test.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The `.jam` importer routed every elbowed connector itself, so one drawn around a layer by hand in
FigJam came in routed through it. FigJam saves the path as `connectorControlPoints`: one per run
between the first and the last, each a point on its run (in the connector's parent's coordinates)
with the run's axis; the first run leaves the start's side and the last arrives at the end's. They
become the `ConnectorPath::Runs` a board connector reshaped by hand has, walked by
`figcraft_doc::route_along`, so the path is kept exactly. Points that don't make such a path (off
both axes, two runs the same way in a row, a first or last run sideways to its side, more runs than
a hand could make) leave the connector to the router as before, and only connectors routed here
count in the "own routing" note. `figcraft_doc::attach_points` and `MAX_RUNS` are exported for it.
Docs: file-format-parity.md, boards-parity.md.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>