Fill, stroke, effect and layout grid rows drag to reorder by their grip (a handle shows over the
row, an insertion line where it lands, the row's label follows the pointer like a dragged layer;
Escape cancels; lists that differ across the selection have nothing to drag). New commands
`fill.move`, `stroke.move` and `grid.move {ids?, from, to}` join `effect.move` (lists bottom first,
one undo step; items keep everything, linked list styles are unlinked).
Variable bindings on list items follow their items when a list is reordered or shrinks:
paint colours bind per paint (`fills/<i>/color`, `strokes/<i>/color`; `fill`/`stroke` stay the
first paint's), so a moved bound paint keeps its variable and its neighbours are never recoloured.
`fill.remove`, `stroke.remove` and `grid.remove` refuse negative indices.
Layout grids get settings in the Design panel (size; count, type, width or height, margin or
offset, gutter; colour) whose numbers bind to number variables (`layoutGrids/<i>/<field>`),
re-applied on variable and mode changes. Instance boolean and text properties bind to boolean and
string variables (`componentProperties/<name>`, from the instance's properties panel), kept across
instance rebuilds, following property renames and dropped on delete, reset and detach.
Inspect names the variable of each paint.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M6: Dev Mode component code, annotations and ready for dev
- Component code: React (new), SwiftUI and Compose write components as reusable
units whose component properties are parameters (variant unions/enums,
boolean flags, text strings, instance swaps as views, slots as optional
content); variant sets share one definition (React: data-* rules for the
other variants); instances are calls passing only the values they change.
Escaped, unique identifiers. `inspect.componentCode` returns the parts.
- Annotations: notes with pinned properties on any layer, saved with the file
(left out when empty), undoable `annotation.add/edit/remove`, `annotation.list`
with live values; drawn as callouts on the canvas in the Inspect tab and
edited in its Annotations section. Instances keep their own annotations.
- Ready for dev: `inspect.readyForDev` lists frames and sections by handoff
status; the Inspect tab sets the status and lists them with a filter.
- Shared widgets: a multi-line field and the status dropdown are reused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M6: test that bound variables become CSS custom properties
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M6: regenerate the parity report
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M2: auto layout keyboard reordering, nested auto layout fixes, section hide contents and dev status
- Arrow keys move auto layout children one place: along the flow (past hidden and
absolute siblings), between wrapped rows, to the neighbouring grid cell, and into
the neighbouring nested auto layout frame; no undo step when nothing moves.
- Sections: section.toggleContents (hide contents), object.wrapInSection, frames in
sections are top-level frames (labels, flows, navigation, PDF export), navigating
to a section shows its frame shown last.
- Ready for dev turns "Changed" when contents are edited; dev.resolveChange with a reason.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M2: README and ROADMAP for auto layout arrow keys and sections
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M7: roadmap refresh after #68–#77
Parity table rows for canvas, selection and transform, layers, effects,
text, prototyping and comments now reflect the merged work; weighted
feature depth ~76%; README command count and hand-off features.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M2: the dev change reason resolves only on Enter or Resolve
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Measuring (Alt/Option): distances from the selection to the hovered layer,
its parent (over the selection itself or empty space) and ruler guides;
Cmd+Alt for nested layers; containment both ways, overlaps between matching
edges, gaps with dashed extension lines; rotated layers and several selected
layers by their bounds; the measured box outlined. Alt while dragging shows
live distances to the neighbours (or the parent's edges); Shift+Alt reads out
an auto layout frame's padding and gaps. Labels are placed so they never
overlap each other or the size label and stay the same size at every zoom.
Commands: measure.distances, measure.padding.
Scale tool (K) canvas handles: one factor about the Scale panel's anchor
(tool.scaleAnchor, shared by the panel and the canvas; the opposite handle when
the anchor is on the side being dragged), Alt from the centre, flips past the
pivot, rotated layers and lines along their own axes, one undo step per drag,
a live factor in the size label and the pivot marked. scale_node now also
scales min/max sizes, layout grids, guides, fixed grid tracks, tiled images,
paragraph indent and run letter spacing.
Selection: Cmd/Ctrl-marquee selects nested layers, Shift-marquee toggles,
the context menu's Select layer lists every layer under the click
(select.layersAt), outlines are purple for every layer inside components and
instances, hover outlines show with the Scale tool too. Fixed marquees
selecting layers that only overlapped the marquee on one axis.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Pins follow their layers (to other pages too) and stay where they were
when the layer is deleted, re-pinned to the nearest surviving parent;
your pins drag onto another frame; crowded pins cluster when zoomed out
and a click zooms them apart; Shift+C shows or hides them.
Threads: edit and delete your own messages (marked edited), emoji
reactions, @mentions with a picker of the names used in the file (your
name from Preferences), read/unread state per name, and Copy link
(figcraft://comment/<id>; pasting it or comment.open goes to the page,
zooms to the frame and opens the thread).
Comments panel: search (text, names, frame), filters (open, resolved,
yours, unread), sort by date or page, each thread's frame and page,
Up/Down to step through threads. The presenter shows the presented
frame's comments (Comments button or Shift+C) to read and reply.
New commands: comment.react, comment.markRead, comment.open,
comment.next, comment.zoom, comment.link, comment.people,
pref.userName; comment.list gains filter/sort/query/frame, comment.move
re-pins. Old files load unchanged (the first message stays on the
comment; missing read state counts as read).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Right-to-left and mixed-direction names in the panels: layers, pages,
Assets, tab titles, the file header and canvas frame titles show in
visual order (Unicode bidi, joined Arabic letters, mirrored brackets),
elided at their end (the left end of a right-to-left name). egui shapes
each font run itself, so shown names are laid out a cluster at a time.
Rename fields keep egui's editing and draw the shown text over it: the
caret, selection, clicks, drags and arrow keys follow what's on screen.
System fonts are added for scripts the built-in UI fonts lack.
The Typography section's text style browser: list and grid views with
each style in its own font, search, folders, this file's and enabled
libraries' styles, apply, detach, edit the typography, create from the
selected chars, hover to preview on the canvas, and "Mixed" when the
chars use several styles. Text styles now link chars (style_id on text
styles), so a range can take a style; editing what a style sets
unlinks the chars edited; files from before link on open.
Live text previews (text.preview / text.endPreview): a hovered text
style, font style or named instance and a dragged axis slider show on
the canvas without an undo step; release commits one step, Escape
cancels. ui.input sends raw window input over the control channel.
Visual QA: list markers keep the paragraph's font, renames start with
the name selected and centred, Max lines shows while truncating.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Ruler guides can belong to a frame: `guide.add/move/remove/clear` take `frame` (as well as
`page`), keeping the guide in the frame's own coordinates. Frame guides move with the frame, span
only it on the canvas, are saved and copied with it, and snap the layers inside it. A guide dragged
out of a ruler and dropped on a frame becomes that frame's guide; its label shows the offset in the
frame.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Responsive presentation: `proto.settings {responsive}` (saved in the
page's prototype settings). The player resizes the presented frame to the
device's screen, or without a device to its whole area at 100%, and lays
it out again exactly as an editor resize does (constraints, auto layout,
text; an axis the frame hugs keeps hugging), live as the window, device or
file change. The player keeps its design document and lays the screen out
from it each time (`cmd::proto::respond`). In the presenter's Options menu
(Responsive beside the size options, or Responsive / Fixed size on a
device) and the Prototype panel.
- Device colours: each device model lists its colour variants
(`proto.settings {color}`), drawn in code as the body's band round black
glass; screen corners are masked exactly. Colour picker with swatches in
the Prototype panel.
- Custom size: a bare screen of any size (`proto.settings {width, height}`
picks it), W/H fields and a swap button in the Prototype panel, imported
from REST `CUSTOM` devices.
- Library collections in the Design panel's Variable modes section, under
their library's name (`library.setVariableMode`; copies whose library
isn't read stay listed).
- Flow descriptions in the presenter's flow picker (and its hover) and under
each flow in the Prototype panel.
- Tests in new modules: engine proto_responsive_tests, ui-egui
proto_responsive_tests; REST device test updated.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
PDF export
- Vector masks clip to the mask's outline, luminance masks are luminosity
soft masks (alpha masks as before).
- Background blur: the backdrop the page draws under the layer is rendered,
blurred and cut to its shape, under the layer's vector content.
- Text hyperlinks become clickable link annotations (one per line and link).
- Frames-to-PDF shares one path (`frames_pdf`): sRGB conversion for Display
P3 files, hidden frames left out, the file name as the PDF title.
- `figcraft-cli export -o x.pdf` and `convert x.figcraft x.pdf` write the
page's frames one PDF page each.
- Tests: mask types, backdrops, links, image deduplication, page sizes in
points, hostile inputs; CLI end to end. Checked by rendering the PDFs back
to PNG and diffing against the PNG export.
Prototyping
- Model: conditional actions (if / else if / else blocks), set variable mode,
and expressions (literals, variable references, + - * /, comparisons,
and/or/not, negate) for set-variable values and conditions; walk helpers
for nested actions (remap on paste, colours, destination checks, library
copies and pruning, canvas connections).
- REST import maps CONDITIONAL, SET_VARIABLE_MODE and SET_VARIABLE with
variable references and EXPRESSION values; unsupported functions (mode
lookups) skip the action with a warning.
- The presenter runs them in order (`figcraft_engine::proto_run`); the
Prototype panel summarises conditionals, edits set-variable-mode, and
`ui.set {protoEditor}` opens an interaction's editor.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Transform box around 2+ selected points (Move tool): corners/edges scale
them (Shift keeps proportions, Alt from the centre), just outside a corner
rotates them (Shift: 15° steps, the box turns with them), dragging inside
moves them; one undo step each. Corners win over points, points over edges,
edges over segments.
- Commands: vector.edit.bounds, vector.edit.resize, vector.edit.rotate,
vector.edit.transform; object.flipH/V and the 90°/180° rotations act on the
selected points while editing (unless ids are given).
- Design panel in edit mode: W/H for the selected points next to X/Y (the
layer's dimensions are hidden while points are selected).
- Point snapping for dragged points and handles and both pens: points and
segment midpoints of this and other vectors, the usual layer targets and
the pixel grid, with guides and a marker on the point landed on; Ctrl/Cmd
turns object snapping off. Snap code generalised in snap.rs (Targets spans).
- Fix: handle/bend drags read positions in the gesture snapshot's coordinates,
so handles no longer lag when the preview re-fits the vector.
- Shared resize math (tools::resized_box) and handle hit-testing for layer
and point boxes; canvas draws both boxes through one helper.
- Tests in vectoredit/transform_tests.rs (incl. junk sweep for the
edit-aware commands).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A stroke can carry a width profile: widths at positions along the path
(each subpath by length), interpolated smoothly (monotone cubic).
- doc: WidthProfile / WidthPoint / WidthPreset (uniform, wedge, taper both
ends, eye, rounded, pinch) on StrokeProps, serde-defaulted so older files
load; paint reach and hit testing follow the widest point.
- pathops: path measuring by length and the variable outline (offset along
the normals, round/miter/bevel joins, caps, dashes keep their width,
Inside/Outside clip as before; bounded samples and dashes, uniform
fallback). Canvas, PNG, SVG (filled outline), PDF and outline stroke use it.
- engine: stroke.widthProfile {preset | points}, node.set widthProfile,
inspect output; the Variable width tool (Shift+W) in vector edit mode:
drag the path to add a width point, slide points, drag their handles,
Delete removes the selected one; vector.edit.widthPoint add/set/remove.
Every gesture is one undo step.
- UI: width profile dropdown with previews drawn in code and a flip button
in the stroke settings; the tool in the edit toolbar (new icon drawn in
code); width handles and a hover preview on the canvas; Position/Width
fields for the selected width point.
- Tests: profile maths, outlines, render regressions, tool gestures,
commands, exports and junk sweeps. ROADMAP, README, docs updated.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Device rotation: phones and tablets turn to landscape (`proto.settings {rotation}`), saved in
the file and imported from REST files (`CCW_90`); the presenter and the preview pane size the
screen to the turned device and draw its body turned. A rotate toggle sits beside the device
picker in the Prototype panel.
- Presentation options: fit, fit width, fill and 100% (`proto.presentOptions {scale?,
deviceFrame?}`, an Options menu in the presenter, Z cycles them, R restarts), the device frame
shown or hidden, kept between presentations; what overflows the window pans by wheel or drag.
Presenter textures are capped to the GPU's limit (big frames at 100% no longer overflow it).
- Scroll momentum: drags fling and slow down exactly per frame-rate-independent sub-steps, the
wheel coasts after it stops, a drag past the ends stretches and springs back; repaints only
while moving, a press catches a fling.
- Variable modes in the Design panel: frames pick a mode per multi-mode collection, or Auto
(inherited); `variable.setMode {mode: null}` sets Auto.
- Tests in new modules (engine proto_present_tests, UI present_view_tests); docs and ROADMAP.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Publishing a library now includes the file's variable collections (modes,
aliases, scopes), versioned per variable and collection; variables hidden
from publishing stay out unless a published variable aliases them or a
published component binds them. Files using the library see its
collections read-only in the Variables editor and the variable pickers;
binding, aliasing or setting a collection's mode copies the variables in
as read-only linked copies, library components keep their bindings, and
"Library updates available" covers changed variables (bound layers follow).
New commands: library.importVariable, library.bindVariable,
library.aliasVariable, library.setVariableMode; variable.update takes
hiddenFromPublishing.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Slots: a frame of a main component becomes a slot property
(component.makeSlot / removeSlot, preferred components, type "slot" in
component.properties). Instances show the main's default content until
the slot is edited; from then on its layers are the instance's own
(override group "slot"): free to add, remove and reorder, kept across
main updates, variant switches, copy/paste and detach, and instances
placed there stand alone. instance.slotInsert fills a slot,
instance.resetSlot brings the default content back. Structure
protection allows edits inside slots only; drops from the Assets panel,
canvas drags, drawing and paste land in slots. UI: slot icon in the
layers panel, dashed outlines and labels on the canvas, slot property
rows with a fill menu in the properties panel, Make/Remove slot on
frames inside a main.
Nested overrides: overrides inside a nested instance survive the main
moving it into another frame (layers matched by source alone when
unique); a nested instance copied out of an instance is re-linked to
its own main with its look kept (values set by component properties
aren't overrides); plain layers copied out of instances stop mirroring
them; canvas drags can no longer pull layers out of an instance or
reorder its auto layout children. Tests for swaps, variant switches,
main restructuring, detach and copy/paste.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* 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's code menu gets "HTML + CSS": the selected layer and
everything under it as nested elements with a stylesheet. Auto layout
frames become flexboxes (fill children flex or stretch, hugging sides
drop their fixed size), other children are placed absolutely in their
parent, text becomes escaped paragraphs, and each layer gets a unique
class named after it. inspect.code takes `language: "html"`.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M5: scrolling inside device frames and a prototype preview pane
On a device, the presented top-level frame is sized to the device's screen:
what doesn't fit is clipped, or scrolls (wheel and drag) when its overflow
allows it. Fixed layers stay on screen, pinned to the edges their
constraints name; "Scroll to" moves every scrolling frame around the layer;
navigating starts at the top unless the new "Preserve scroll position"
option keeps it.
The preview pane is a resizable window over the canvas running the same
player (present.rs now shares layout, drawing and input between the
full-window presenter and the pane). It follows edits live, keeps the
current screen while it exists, shows a newly selected top-level frame, and
takes the keys its prototype uses only while hovered. Opened from the play
menu, the Prototype tab (flows, settings, frame) and `proto.preview
{start?}` / `proto.closePreview`; the control channel gets `ui.set
{preview, presentScroll}` and the player's state in `ui.inspect`.
Alt+Cmd+Enter now presents.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M5: reconcile with main: one device list, REST navigate keeps preserveScrollPosition
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 layers in and under the selection that have
export settings, with their formats and sizes. Click one to export it,
or Export all from the section header (one file through a save dialog,
several into a folder). New command inspect.assets.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Layout (crates/layout):
- Fill children take their size before the frame is sized, so wrapping fill
text, frames that hug it and grid rows settle in a single pass (three
levels deep included).
- Fill children share space like CSS flexbox: min and max violations are
resolved in turn instead of freezing both at once.
- A single child with space-between sits at the start; a wrapped row of only
fill-height children keeps their height; grids size fill-width children
before their rows.
- coherent_sizing: Fill only in an auto layout flow on an axis the parent
doesn't hug, Hug only on auto layout frames and text (a text hugs what its
resize mode grows); anything else becomes Fixed at its size.
Engine:
- Setting a child to Fill turns a hugging parent Fixed on that axis; a parent
that starts to hug, layers leaving auto layout and layout.remove fix the
rest (applied in normalize).
- Moving layers: the deepest auto layout frame under the pointer wins (plain
frames in a flow are items, not destinations; the dragged layers and their
descendants are skipped); the frame they come from keeps them near its
edge. They take the slot (or grid cell) there with the insertion line drawn
in the gap and the receiving frame outlined; out of auto layout they become
positioned children of the canvas or plain frame at the drop point. One
undo step per drag. Slots skip hidden and absolute children.
Tests: crates/layout/src/nested_tests.rs, crates/engine/src/al_nesting_tests.rs.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M6: REST import of prototypes and variables; export panel polish
REST API file JSON import now brings prototyping and variables along:
- interactions: on click/hover/press/drag, after delay, mouse enter/leave/down/up and key
triggers; navigate, open/swap/close overlay, back, scroll to, change to, open link and set
variable actions (lone unconditional blocks inlined); dissolve, smart animate, move, push and
slide transitions with easing incl. custom bezier and spring; the legacy transitionNodeID
- flow starting points (and the deprecated start node), overflowDirection, scrollBehavior,
prototypeDevice and prototype background
- the local variables response (collections, modes with the default first, values, aliases,
scopes), passed as `variables` to file.open / file.import (object, JSON text or path), found
next to the file (<name>.variables.json / variables.json) or embedded; boundVariables, paint
and text style bindings and explicitVariableModes map onto the fields variable.bind uses
- file.import appends variables, flows and prototype settings with the pages
- unsupported parts (video, gamepad, conditionals, variable modes, library variables) warn
Export panel: rows with a typed or preset size (2x, 512w, 512h), suffix and all five formats
through the new export.update command; export.add steps up (1x, 2x @2x, ...); a preview;
"Export N layers" through the new file.exportLayers (one file per setting, unique safe names)
in the panel and the Export dialog; file.export takes `size`. MCP open_file/export pass the new
params.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M6: Inspect tab: properties with styles and variables, redlines on hover (#41)
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>
---------
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>
Every 30 minutes, each open document with unsaved changes since its last
version gets an automatic "Autosave" version (flagged `auto` in the file).
The newest 20 autosaves are kept; older ones make room and user versions
are never dropped for them. Runs of autosaves fold into one line in the
Version history panel. New command version.autosave.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Clicking a version in Version history shows it in place of the document,
read-only, with a banner to restore it or go back to the current version
(clicking Current version or closing the panel goes back too). Edits,
undo and gestures are refused while a version is shown; saving, closing
and restoring end the preview first, so the document and its unsaved
state are never touched by it.
New commands version.preview and version.exitPreview; version.list marks
the shown version.
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>
- Object ▸ Resize to fit (Alt+Shift+Cmd+R, `object.resizeToFit`): frames, sections and
components shrink or grow to their visible contents without moving them on the page; auto
layout frames switch to hug instead.
- Sections get colour swatches (light tints) in the Design panel.
- Frames and sections get a handoff status (`devStatus`: ready for dev / completed) set from
the panel and shown as a badge next to their name on the canvas; `node.get` reports it.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Eraser tool (Shift+E) in the vector edit toolbar: drag a brush over paths to remove the parts
it covers; segments are cut exactly where the brush enters and leaves (curves stay curves),
fully covered segments and orphaned points go, and closed shapes open while keeping their
implied fill. A stroke is one undo step; the brush shows under the pointer.
- `vector.edit.erase {points, radius?}` for the CLI, control channel and MCP.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Versions now belong to the document (DocState.versions) instead of a process-global map keyed by
document uid, so they survive closing the file. A new, renamed or deleted version marks the file
as changed.
- .figcraft: `versions/index.json` (id, name, description, time, image hashes) plus one deflated
`versions/<id>.json` snapshot per version; images only versions use are stored once and kept with
them. Snapshots read from a file are parsed on first use (and written back as-is). Readers that
don't know versions ignore the entries; files without versions load unchanged. Damaged, missing,
oversized or duplicate entries are skipped and reported, never failing the load. At most 100
versions per file; names and descriptions are clipped.
- Commands: `file.saveVersion {name?, description?}` returns the version; `version.list`;
`version.restore {id | index}` (one undo step, keeps the file name, stays on the current page);
`version.rename {id | index, name, description?}`; `version.delete`; `version.duplicate` (opens
the snapshot in a new tab). Indexes are validated: a negative index used to restore version 0.
`req_index` param helper shared with the guide commands.
- Version history panel in the left sidebar in place of Pages and Layers (File > Show version
history; Save to version history opens it with the form): save form (title, description),
"Current version" on top and the versions newest first on a timeline with name, relative time and
description; per-version menu (also right-click): Restore, Rename (title and description inline),
Duplicate, Delete (confirmed inline). New shared `widgets::button`.
- `figcraft-cli run --out x.figcraft` keeps the version history.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- A nested instance inside a main component can be exposed (`component.exposeInstance`, an
"Expose properties to instances" toggle in its panel); the main's instances then list it by
name with its variant and component property controls, recursively for exposed instances
inside it. Values set there are nested overrides and survive main edits.
- `instance.exposed {id}` lists an instance's exposed nested copies (for the CLI and MCP).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Layers gain `maskType` (alpha by default, so files are unchanged): a vector mask clips by the
mask layer's outline whatever its fills and opacity; a luminance mask shows content where the
mask is bright and hides it where it is dark.
- `node.set {maskType}` (validated), `node.get` reports it, and the Appearance section shows a
Mask dropdown when the selection is masks.
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>
- New `figcraft_format::rest`: imports the public REST API file JSON (`GET /v1/files/:key`, and
the nodes endpoint's response) using the published reference only. Pages (name, background,
flow starting points), the layer tree, names, visibility, lock, opacity, blend mode, masks.
- Geometry from `relativeTransform` (children of groups/booleans checked against
`absoluteBoundingBox` to settle their space), else absolute bounds + rotation; sizes, corner
radii (uniform and per corner), smoothing, clip content, constraints, export settings, layout
grids.
- Paints: solid, linear/radial/angular/diamond gradients (handles map 1:1), image fills (fill,
fit, tile, stretch, rotation, filters) with bytes from a resolver or a grey placeholder keyed by
the imageRef; paint opacity, visibility and blend. Strokes: weight, per-side weights, align,
caps, join, dashes, miter angle. Effects: drop/inner shadow (offset, radius, spread, show behind),
layer and background blur.
- Vectors from `fillGeometry` (stroke-only vectors use the `strokeGeometry` outline), ellipse arc
data, stars/polygons, boolean operations. Text: characters, type style (family, style or weight
+ italic, size, line height px/%/auto, letter spacing, case, decoration, alignment, auto resize,
truncation, paragraph spacing/indent, OpenType flags, links), per-character overrides → styled
runs (UTF-16 aware), list type.
- Auto layout: direction incl. grid, alignment, padding, gaps, wrap, hug/fixed/fill sizing (legacy
and per-axis fields), absolute positioning, min/max, grid cells.
- Components and sets with descriptions and property definitions (names lose their `#id`
suffix); instances of components in the file become instances with their property values and
listed overrides; outside components and component cycles stay frames. Shared styles are created
from the layers that use them, named from the metadata.
- Unknown types and fields are skipped with a counted warning list; sizes, depth (256), layer count
(200,000), path length and list lengths are capped; nothing panics on junk.
- Engine: `file.open` / `file.import` / CLI detect the JSON by content, link imported instances to
their mains (sources and overrides), read image fills from an `images` folder next to the file,
never save over the JSON, and return `warnings`. `file.import` now remaps ids across all
imported pages at once (instances keep their main and layer sources) and brings styles and page
backgrounds along. `figcraft-cli info` prints load notes.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Inline IME composition: egui Preedit events become a composition held in the text edit
session (`TextEdit.preedit`), shown inline at the caret with an underline and the IME cursor,
reflowing the text; the document only gets the committed text (`text.edit.insert`). Composing
over a selection replaces it; moving the caret or ending the edit drops an uncommitted
composition. The IME candidate window follows the cursor inside the composition.
New command `text.edit.preedit {text, cursor?}`; `text.edit.selection` reports it.
- Variable fonts: faces read their fvar axes (tag, name, range, default, hidden) and named
instances. Named instances are offered as styles and resolve in `find`; a style name with no
instance implies weight/italic/slant on the axes and optical size follows the font size.
Per-run custom axis values (`TextStyle.variations`, serde default, omitted when empty) are applied
in HarfRust shaping (ShaperInstance) and skrifa outlines/advances (cached per coordinates).
PDF export outlines non-default instances instead of embedding the default one.
- Commands/props: `fontVariations` / `variationSettings` props (whole layer or start..end),
`text.setVariations {variations, replace?}` (merges; edited range while editing),
`text.fonts {family, style?, axes: true}`; `node.get` and copy/paste properties carry the axes.
Picking a named style (Bold/Italic toggles, weight steps, fontStyle/fontWeight) drops custom
wght/ital/slnt values and keeps the others.
- Type settings ▸ Variable tab for variable fonts: a slider (default tick, double-click to reset)
and a field per visible axis, "Mixed" across differing styles, live drag as one undo step,
"Reset to style". Long style/family names stay on one line with an ellipsis and a tooltip.
- Tests: variable font shaping/outlines/clamping/instances (skipped without a variable font),
serde backward compatibility, preedit lifecycle and undo, variation commands/props/undo/junk;
junk sweeps extended with variation and preedit params.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Assets tab rebuilt as its own module: local components as two-column tiles with live
thumbnails (re-rendered when a component's content changes, throttled), a search field that
filters components and styles, and the local styles list.
- Drag a tile onto the canvas: a ghost follows the pointer, the frame under it highlights and auto
layout frames show the insertion line; releasing drops an instance there, centred on the
pointer. Clicking a tile places one in the middle of the view.
- New command `instance.place {component, x, y}` (page point; drops into the frame or auto layout
slot under it; never inside its own main) for the CLI, control channel and MCP.
- The auto layout drop slot is shared with moving layers (`drop::auto_layout_slot`).
- Creating a component from a single shape names it after the shape.
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>
- Edit-mode toolbar (Move V, Pen P, Bend hold Cmd, Paint bucket Shift+B, Lasso Q, Cut X, Done)
replaces the tools while a vector is edited.
- Pen in edit mode continues open ends, branches from any point (vector networks), adds points on
segments and closes paths into fill regions; dragging pulls out mirrored handles.
- Paint bucket fills or unfills any closed area: faces of the network (half-edge walk, nested
pieces become holes), with a hatched preview of the area under the pointer.
- Bend tool / Cmd: bend segments, pull handles out of a point, click a smooth point to sharpen it.
- Lasso and marquee point selection, segment selection (shift adds), cut at a segment or a point.
- Per-point handle mirroring (none / angle / angle and length, inferred for existing files),
Alt-drag breaks it; smooth/sharp points; join and merge selected points (Cmd+J, Shift+Cmd+J).
- Delete opens the path, Shift+Delete heals it; arrows nudge points; Cmd+A selects all points.
- Design panel shows the selected points' position, corner radius and mirroring.
- Every gesture is one undo step; edit mode ends when its layer is undone away.
- Paths that end on their start without a ClosePath (ellipse outlines) stay closed and filled.
- New commands (vector.edit.tool/pen/paint/cut/lasso/mirroring/smooth/position/nudge/connect/...)
for the CLI, control channel and MCP; docs updated.
- Clippy: simplify a few boolean expressions flagged by the current toolchain.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>