* M6: .fig export writes a page's prototype background (#2326)
The export writes a page's prototype background as prototypeBackgroundColor (field 141, a plain colour) when it differs from the default, as Figma does for a page whose background was set. The device-settings warning now fires only for the device, rotation, colour, custom size and responsive settings. Measured on a synthetic file saved from Figma's web app.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 measured and wrote at medium
* M6: the .fig export writes a prototype background opaque and warns for its opacity, and its tests read the written record (#2326)
The colour is written with alpha 1, as the measured record has it and as the import reads it, and a background that isn't opaque warns that its opacity was left out. The tests now read the written record, and each device setting alone is checked.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.2 reviewed; Sonnet 5.5 wrote at medium
* M6: the .fig export writes every page's prototype background, so Figma shows the same colour (#2326)
A page at FigCraft's default background used to write no field, so Figma showed its own default instead. Every page now writes its colour, opaque. The test for a default page reads the written record and the import.
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
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* M6: .fig export writes list spacing, hanging punctuation and lists, and vertical trim (#2305)
The export wrote none of a text's list spacing, hanging punctuation, hanging lists or vertical trim, although the import reads all four. It now writes listSpacing, hangingPunctuation, hangingList and leadingTrim, each only when it differs from its default (as Figma saves them), and an instance override back to the default clears the field. The warning narrows to OpenType features, which the import does not read from a .fig.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.2 briefed; Sonnet 5.5 wrote at medium
* M6: test that an instance override back to the default clears it (#2305)
Adds a round-trip test for the unset entries: an instance whose text goes back to the defaults must not take the main's list spacing, hanging flags and vertical trim again. Also scopes the comment and the docs row to a design file: a text at the defaults carries none of the four there, and a text style holds none of them.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.2 briefed; Sonnet 5.5 wrote at medium
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Each vector region with paints of its own (an empty list included) now names an entry of the network's style table, so the paints come back on import; equal lists share one entry and a region without paints writes style 0. Only the paint frame of a region, which the importer cannot read, still warns.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.2 briefed; Sonnet 5.5 wrote at medium
An image paint's adjustments (exposure, contrast, saturation, temperature, tint, highlights, shadows) were dropped on export with a warning, although the importer already reads them. The export now writes them as the paint's `paintFilter`: seven floats, with saturation as vibrance and the tint's sign flipped to the file's (positive towards magenta). A paint without adjustments gets no `paintFilter`. The warning is gone.
The schema gains `PaintFilterMessage` and `Paint.paintFilter`. A round-trip test turns the importer's fixture around and pins the written values and field numbers. Not checked in other software.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.2 briefed; Sonnet 5.5 wrote at medium
The engine test now follows the issue: a list typed at 5, saved as .fig and opened, split by Backspace at the start of "b". The REST fixture is a top-level list, the junk sweep reaches the new loop through a listed text layer, and the document crate tests the new rule for nested, bullet, plain and over-deep levels. ParaList gets its doc comment back, and the revision row names the item written with 0.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.2 queued; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
Figma writes a list's start on every line of the list (measured in Figma's web app, 2026-11-12, from a saved .fig: a list started at 5 has listStartOffset 4 on all its lines, and the item split off by Backspace is written with 0). The import kept every line's value, so after a split the items after it restarted at the old start instead of 1.
The import now keeps a start only on the item where a numbered list starts counting, and gives every item that goes on counting 0, for .fig and REST listStartOffsets alike. The rule is one function in the document model, list_item_starts_counting, which the layout's list numbering now uses too. The export is unchanged and its re-import now equals what went in.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.2 queued; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
A native shape frame now writes fixed sizing on both axes, as a shape made here has, so the first edit no longer shrinks it to hug its text. A shape whose generated text is blank while the outer layer still carries text keeps the generated layers (#1397). The kind name in the no-outline note is clipped, the docs rows match the code, and the new key sits in order.
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
On a whiteboard, a FigJam shape with text whose generated records are complete becomes a native board shape for the 23 kinds FigCraft draws: an empty centred frame holding FigCraft's own outline (the file's paints, weight and dashes kept) and the text. The 7 kinds without an outline, and shapes with damaged records, keep the generated layers; the former warn with the kind's name. Stickies and shapes share one native-board hook in the layer mapper.
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
A boolean group takes a corner radius (and corner smoothing): the Design panel shows the field for it, `node.set cornerRadius` writes it, and the renderer rounds every sharp corner of the group's result after the fold, convex and concave alike. Layers inside keep their own radius and do not stack with it, a radius larger than the edges allow shrinks to fit while the field keeps the typed value, Flatten bakes the arcs, and a mixed selection reads Mixed. Measured in Figma's web app on 2026-11-10.
The rounded result is cached apart from the fold (keyed by the folded path, radius and smoothing), so scrubbing the radius never redoes the boolean steps. The .fig and REST importers and the .fig exporter now carry the radius and smoothing of a boolean group, and paste properties and radius variables reach it. Docs: ui-parity, target-app-parity, gaps, file-format-parity, ROADMAP, mcp.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.1 designed; Sonnet 5.5 wrote at medium
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.
FigCraft chose the first face added for a family and style, so the embedded Inter 4.x kept Regular, SemiBold and Bold after Inter 3.19 was loaded and only Medium refitted. A loaded font is the counterpart of a font uploaded to the organization, which Figma's help center ranks first ("Manage conflicting fonts"). The test builds its faces from any embedded or installed font (skipped when there is none) and checks a loaded face beats a built-in one, the latest load beats earlier ones, and other styles resolve as before.
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
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
The export tool and command descriptions, docs/mcp.md and the file-format
table said PDF is always sRGB, that sRGB JPG is untagged and that every
exported file is tagged with its profile. RGB PDF is sRGB and PDF with cmyk
is DeviceCMYK; JPG embeds an ICC profile for sRGB and Display P3; WebP embeds
one for Display P3 only. The JPG quality (1 to 100, default 90) now has one
row in the format table.
file.export always encoded JPG at quality 90, and an sRGB document's JPGs
carried no colour profile, so viewers could only assume sRGB. PNG exports
always get an sRGB chunk, and Display P3 JPGs already embedded their ICC
profile.
- ExportSetting gets a quality (1 to 100, default 90, saved only when it
isn't 90), parsed with the other export options: file.export, export.add,
export.update, file.exportLayers and MCP export take it.
- encode_export_with takes the quality; encode_export keeps quality 90.
Every JPG now embeds the document's profile, sRGB included.
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>
An end whose magnet is AUTO has no fixed point: FigJam picks the side when it draws, and the file
keeps whatever relativePosition the end last had, so the importer sent every automatic end out of
the stale side (the left one in the reporter's file). AUTO and CENTER ends now attach to the side
facing the other end, as a board connector drawn here does, for straight, elbowed and curved
connectors alike; named magnets and ends without one are unchanged.
Test: connectors_with_an_automatic_side_leave_the_side_facing_the_target (the fixture schema gains
the ConnectorMagnet enum). Docs: file-format-parity .jam row, status line and revision history.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M8: .fig import: a routed connector takes its line style from the generated record (#1807)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M8: .fig import: a routed connector keeps its own empty paints and dash pattern
CodeRabbit review on #1813: an explicit empty strokePaints (no stroke) or
dashPattern (solid) on the connector no longer takes the generated record's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Each export setting has a color profile: Same as file (the default, the
document's profile when it exports), sRGB or Display P3. PNG, JPG and
WebP settings list it in their advanced settings, and export.add,
export.update, file.export, file.exportLayers (for every setting) and
the MCP export tool take it as colorProfile. The rendered pixels are
converted to the chosen profile (out-of-gamut colors clip) and the file
is tagged as before: an sRGB chunk for sRGB PNG, the generated ICC
profile (and cICP in PNG) for Display P3; sRGB JPG and WebP stay
untagged. A setting keeps its choice when its format changes, and
settings saved without one read as Same as file.
SVG, PDF and Android XML show no choice and stay sRGB; PDF has no
output intent yet.