* M3: colour profiles: sRGB or Display P3 per file, convert or keep values, tagged exports
- color: ColorProfile (srgb, displayP3) with conversions derived from the primaries (shared D65
white and sRGB curve; out-of-gamut channels clip), table-driven pixel conversion, a generated
ICC v4 matrix/TRC profile and PNG cICP code points.
- doc: Document.color_profile (serde default sRGB, written only when set); every colour value
(paints, text runs, effects, grids, page and prototype backgrounds, overlay backdrops, styles,
colour variables, document colours) can be mapped; set_color_profile converts or reinterprets.
- engine: file.colorProfile {profile?, convert?} (File > Color profile...; without a profile it
opens the dialog); document.inspect reports colorProfile.
- render: images are taken as sRGB and managed into the file's space; encode_export tags PNG
(sRGB chunk, or iCCP + cICP for Display P3) and embeds the ICC profile in Display P3 JPG/WebP;
SVG and PDF export Display P3 files converted to sRGB. The CLI exports the same way.
- ui: Color profile dialog (keep appearance / keep values) and a Color profile row on the page
panel; the canvas, presenter, thumbnails, swatches and colour picker show a Display P3 file's
colours converted to the screen's sRGB, and Copy as PNG converts too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: image adjustment presets: built-in, saved in the file, reset
- color: ImageFilters::PRESETS (Vivid, Muted, Warm, Cool, Black & white, Fade, Reset), lookup by
id or label, and sanitized values.
- doc: Document.image_presets (name + adjustments; written only when there are some).
- engine: image.adjust {ids?, target?, index?, preset?, filters?} sets the adjustments of image
paints from a preset (built-in or saved) with values on top, one undo step; image.savePreset,
image.deletePreset and image.presets.
- ui: the colour picker's image section gets a presets menu named after the preset in effect
("Custom" otherwise), saved presets with delete, "Save as preset...", a Reset button, and
aligned adjustment sliders.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: style picker: search, folders, list or swatch grid, edit, create, detach
The Fill, Stroke, Effects and Layout grid sections' Styles button opens a popover laid out like
the variable picker (crates/ui-egui/src/style_picker.rs):
- search over style names; styles grouped by the folders of their `/`-separated names, which
collapse (searching opens them); colour styles as a list or a grid of swatches;
- click applies, a check marks the applied style; the row's edit button (or a right-click) edits
the name, description and values: colour style paints open the colour picker on the style,
effect and grid styles update from the selection; delete;
- `+` creates a style from the selection's current values and opens it for naming; detach;
- colour slots still list colour variables to bind;
- the applied style chip opens the picker; new icons Adjust, ViewList, ViewGrid (drawn in code);
- control channel: ui.set {stylePicker, stylePickerEdit, stylePickerView}.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Inspect tab lists the selected layer's properties for handoff:
layout (size, position from the parent, rotation, opacity, radius), auto
layout (direction, gap, padding, alignment), typography, fills, strokes
and effects, each with the style or variable it comes from. Clicking a
value copies it. With the Inspect tab open, hovering measures distances
from the selection without holding Alt.
New command inspect.properties returns the same groups for agents.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The selected shapes' outlines split the canvas into regions, computed as the faces of their
arrangement with linesweeper (curves kept, per-shape fill rules, holes and islands handled) and
cached per document version and selection.
- Engine: shapeBuilder.regions / merge / remove / mode commands, ToolId::ShapeBuilder and
tool.shapeBuilder (catalog: Tools > Shape builder, Shift+M). Merge makes one vector from the
picked regions styled like the top-most shape over the first; every shape covering a picked
region loses that area (becomes a vector, or is deleted when nothing is left). One gesture or
command is one undo step. Session::tool_key routes Enter (finish) and vector edit keys.
- Pathops: faces() (arrangement faces with their covering inputs) and union_faces().
- UI: region edges on the canvas, hovered/collected regions hatched (danger colour to remove),
no transform box while building, a toolbar pill with Merge/Remove modes, a hint and Done; shared
pill/tool-cell/divider/Done helpers; original merge/remove icons drawn in code.
- Tests: pathops faces; engine regions, merge, remove, curves, groups, rotation, pointer
click/drag/Alt, modes, Escape/Enter, cache, junk parameters and degenerate geometry.
- Docs: ROADMAP, README, docs/mcp.md, docs/control-protocol.md, docs/parity.md.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Publish (`library.publish {description?, include?, exclude?}`, Libraries > Publish library…):
the file's components, component sets and styles (by default all but names starting with `.` or
`_`, or what the last publish offered) and every component and style they use go into a snapshot
with a manifest (library id, version, per-asset version stamps). An asset keeps the version it
last changed in; publishing without changes is refused.
- .figcraft: `library/manifest.json`, `library/document.json` (the snapshot) and
`library/images/<sha256>` for images only the library still uses. Older files and readers are
unaffected (extra members); a damaged library is skipped and reported when the author's file
opens. Files using a library read only these members (size-limited), never unpublished edits.
- Version history: the library entries are written and read alongside the versions (a library
survives saving with versions and reopening); restoring a version keeps what the file published.
- Enable: `library.add {path}` / `library.remove {id}` / `library.list {assets?}`; the document
keeps its enabled libraries (id, name, path, last seen version) in its data. The session caches
each library file and reads it again when it changes on disk; in the browser these commands
return a clear error (no file system).
- Use: `library.place {library, component, x?, y?}` / `library.import` copy the component (with
the components and styles it uses) into a hidden library store (a page-like root outside the
page list) as read-only main components and place an instance; instances behave like any other
(overrides, properties, variants, swap, reset, detach). `library.applyStyle` copies a style in as
a linked remote style. Editing the copies or library styles is refused; Go to main component
names the library. Importing a file's pages brings its library copies along (shared with copies
the document already has).
- Updates: `library.updates` and `library.acceptUpdates {ids?}` re-import changed copies in place
(layer ids kept, so instance overrides survive, also when layers are added); unused copies are
dropped when a library is removed or updated.
- Assets panel: each enabled library's components (thumbnails, search, click or drag to place,
shared with the local tile code) and styles (click to apply, paint swatches), collapsible per
library; "Library updates available" banner with Update; a libraries view (book button,
Libraries menu) to publish this file (choose assets, description) and add or remove library
files. Local styles in the panels list only this file's styles.
- Tests: manifest round trip and versions, add/place/update keeping overrides (incl. a structural
update), variants/swap/undo, library styles and importing pages, missing/corrupt/truncated/
oversized/never-published files; format tests for damaged and oversized members.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Local variables window becomes a table editor: collections in a
sidebar, variables grouped by name with one value column per mode, typed
value cells (colour swatch + hex + alpha, number, string, boolean), alias
chips with a variable picker and detach, double-click rename, context
menus, and an Edit variable window for description and scopes.
New commands: variable.update (name, description, scopes),
variable.duplicate, variable.renameMode, variable.removeMode,
variable.renameCollection, variable.deleteCollection. variable.set
validates aliases (same type, no loops) and stores them by id; addMode
always picks a fresh mode id. Pickers offer only variables scoped to the
property. ui.set {variables} opens the editor on a collection.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Code generation moves from the UI into the engine (`codegen.rs`): CSS for the layer as before,
plus SwiftUI and Compose for its subtree (stacks with spacing and padding for auto layout,
offsets inside a ZStack/Box otherwise, text with font and colour, shapes, fills, strokes,
shadows, opacity; other layers become image-asset placeholders). String literals are escaped
per language; deep trees are capped.
- New command `inspect.code {id?, language?}` for the CLI, control channel and MCP.
- Inspect tab: a language picker and Copy for the shown code; Copy as CSS uses the same code.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Min/max width/height: the W/H sizing menus add min/max (at the current size) and remove them;
limit rows edit or add the missing bound. The layout clamps Fixed, Hug and Fill children and
the frame itself; a hugging text wider than its max width wraps at the max. min > max is kept
ordered by node.set (the other bound follows); in the layout min wins.
- Ignore auto layout (absolute position): a toggle in the Position section; the child stays
where it is, can't Fill, shows constraints and follows them when the auto layout frame resizes
(by hugging, filling or a direct resize).
- Text baseline alignment for horizontal layouts ("Align text baselines"; crossAlign baseline):
first-line baselines line up, other layers align to the top, nested auto layout frames use
their first child's baseline. Measure gains text_baseline.
- Grid auto layout: column/row tracks (fixed px, hug, fr fill) via columnTracks / rowTracks or
layout.setTrack, cells anchored by gridColumn / gridRow with gridColumnSpan / gridRowSpan,
auto placement in reading order, rows grow to fit, hugging grids size to their tracks.
Panel: column/row gap, track size rows, a child's cell and spans. Old files load unchanged
(serde defaults; missing tracks are 1fr).
- Canvas handles: wrapped-row gaps and grid column/row gaps are draggable; a selected grid's
tracks are outlined.
- The layout section moved from right.rs to layout_panel.rs; inspect reports min/max and a
grid child's resolved gridCell; docs list the new props.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>