| 2026-10-30 |
minor |
M3, gap 14: a collection of this file can be extended, as the help center shows (right-click a collection, Extend collection); before, only a library's could. variable.extendCollection {collection, name?} makes " (extended)" with its modes and one inheriting variable per variable, and the Variables editor's collection menu offers it beside Rename and Delete collection. Edits of the collection it extends reach it at once (values in modes it doesn't override, new variables and modes, renames, mode order), so normalising keeps extensions in step whenever variables change, not only on library updates; undo takes both back. An extended collection extends again (parents settle first; an extension doesn't take its parent's overrides as its own), and a loop of extensions from a hostile file comes apart into plain collections keeping their values. Deleting the parent leaves a plain collection with the values it showed, as for libraries. Tests: extended_chain_tests (engine), variables_rename_tests::a_collection_of_this_file_extends_from_its_context_menu (headless UI). Grade unchanged |
| 2026-10-30 |
minor |
Library analytics count detaches: a file that detaches an instance of a library component counts it (kept with the file) and its app reports the counts to the library's server beside its usage; Show usage and library.usage list them per component and per file (detached, detachesKnown). Older servers and apps are unaffected: the reports are new messages and the old ones keep their encoding. Still nothing over time. Numbers unchanged |
| 2026-10-30 |
minor |
M3, #1613: a variable, collection or mode named like another one's id no longer takes its place where an id is stored or typed (expressions, prototype mode switches, font family variables, the panels' labels); names still work where a person types them. Grade unchanged. |
| 2026-10-30 |
minor |
M3, #1280: a colour variable can be calculated from others, per mode: {"alpha": {"variable", "opacity"}} is that variable's colour with its alpha replaced (replace, not multiply, so re-editing the source's alpha never compounds), {"mix": {"from", "to", "t"}} blends two operands ({"variable"} or {"literal"}) per channel over the stored values in the document's colour profile, alpha included (converting the profile keeps a mix's ends exact and moves its colours in between slightly). Calculations resolve lazily like aliases, so bound layers, mode switches, instances and undo follow their sources; they compose (a calculation of a calculation), a loop is refused when it is set, and one resolve consults at most 32 variables. Files keep them; REST, CSS, Penpot and .fig exports write the colour they show in each of their collection's modes (other collections in their default modes, so a CSS calculation doesn't follow another collection's data- mode); deleting a source leaves its readers on the colour they showed; library import and swap re-point their operands. The panel shows them read-only (swatch and formula) and can swap one for an alias or detach it to its colour. Tests: engine/src/variables_colour_tests.rs, doc/src/tests.rs. Left: editing calculations in the panel; files using them need this build to open. Numbers unchanged |
| 2026-10-30 |
minor |
M3, follow-up to #1542: stored variable and collection references use exact ids in export, library copies and swaps, imported aliases, alias deletion and cycle checks, Dev Inspect, the library mode picker, and prototype actions and expressions. Interaction edits resolve only incoming names and leave stored references alone. Command arguments still accept names as before. Tests: the stored_variable_ids_* tests in the doc, engine, format and ui-egui present and responsive suites. Numbers unchanged |
| 2026-10-30 |
minor |
M8: the component set's Properties section fits the narrowest right panel: variant values wrap onto more lines and every + and − button stays visible. Grade unchanged. |
| 2026-10-30 |
minor |
M8, #1097: library updates from a team library a file names are offered, not fetched from its server by themselves: only team libraries you added or checked are checked in the background (Check for updates in Assets ▸ Libraries). Numbers unchanged. |
| 2026-10-30 |
minor |
M8, #1114: variables import from an explicit file path (variable.import, the variables of file.open and file.import) takes regular files of at most 256 MiB, the limit the sibling variables.json already had; anything else is refused before the document changes. Grade unchanged. Tests: import_export_tests::an_explicit_variables_path_is_capped_like_the_sibling_file, variables_io_tests::variables_import_caps_the_file_it_reads |
| 2026-10-30 |
minor |
M3, #1536 and #1537: component properties are listed in the order they were made in, and renaming one keeps its place (they were sorted by name, so a rename moved the property); that holds for every kind, on every variant of a set and on instances, in component.properties, the Design panel and .fig export. Files from before keep their properties by name until one is added or renamed. Make slot (the Slot menu of a frame inside a component) asks for the slot's name first: a field under the Slot row with the frame's name selected, Enter or Make slot to make it, Escape to cancel; a name taken keeps the field open. Renaming stays in the component's Properties list. Tests: engine/src/prop_order_tests.rs, ui-egui/src/make_slot_naming_tests.rs. Left: dragging the shared properties to reorder them, and renaming a slot with only its frame selected. Numbers unchanged |
| 2026-10-29 |
minor |
M3, #1338: the instance-swap property's menu (on an instance, its main component's default and the component playground) and the instance's swap menu open with a search field at the top that has the keyboard; typing filters the components by name (any case, headings without matches drop out), an empty result reads "No matching components", Up and Down step through the matches and Enter picks one (the first without a step), Escape clears the search and then closes the menu. Only the list scrolls; the menu grows back when the search is cleared. Tests: component_swap_search_tests. Numbers unchanged |
| 2026-10-26 |
minor |
An instance-swap property's default menu scrolls (#1338): on the main component's Properties row the menu listed every component in the file at full height, so in a file with many (an icon library) the bottom ones ran off the screen and couldn't be picked. It now scrolls past 320 pt like the instance's swap menu, is at least 200 pt wide, ticks the current default and is named for assistive technology (component_swap_default_tests). Left: searching these menus. Numbers unchanged. |
| 2026-10-24 |
minor |
Variables on a text style's fields from the style editor: the font and font style menus start with Apply variable (string variables), and the size, line height and letter spacing fields show a variable button on hover (number variables). A bound field shows its variable in place of the value; a click on it picks another variable and its detach button unbinds it (style_picker_tests). Left: the other effect and grid fields (commands only), library variables in the picker. Numbers unchanged. |
| 2026-10-23 |
minor |
Instance-swap properties in the component playground: the Inspect tab's Playground shows a dropdown for each, with the same menu as the Design panel's (the property's preferred components first, then every component in the file). The component picked changes the preview and the code only, never the file. Left: slots in the playground. Numbers unchanged. |
| 2026-10-20 |
minor |
A main component's copy within its file is an instance of it (#892): Duplicate (edit.duplicate) and Alt-drag on the canvas made a second main; they now place an instance where the copy went (same offset, the instance's default name, selected), as Copy then Paste already did. A variant duplicated in its set stays a new variant. |
| 2026-10-18 |
minor |
A bound colour variable's alpha applies once (#802): a #RRGGBBAA paint keeps its alpha in the paint opacity, and binding a variable with alpha left that opacity in place, so a 50% fill bound to a 50% variable drew at 25%. Binding (variable.bind, node.set boundVariables, style.bind) now restarts the paint's opacity at 100% and unbinding (also deleting the variable) moves the colour's alpha back into the opacity; .fig and REST imports take the variable's alpha back out of a bound paint's opacity, so .fig round trips no longer square it. Colour-picker edits of a colour variable or a plugin colour field, generated CSS strokes and the document colours list counted the paint opacity twice and now count it once (bound_alpha_tests). Numbers unchanged |
| 2026-10-18 |
minor |
Variables import (#790): a variables map outside meta (or meta.variables next to top-level collections) is read, and an import that finds no variables, or fewer than the collections' variableIds list, says so in warnings instead of succeeding silently |
| 2026-10-17 |
minor |
Variant properties reorder by dragging them in the Properties section, one undo step (#679; was "no drag in the panel yet"); a library file saved again right after it was read is read again, so a quick republish isn't missed (#652); the Hidden from publishing row no longer says the Hide when publishing item is missing (it landed 2026-10-12). Numbers unchanged |
| 2026-10-17 |
minor |
Size and resizing overrides of layers inside instances (#731): a layer in an instance can hug, fill or keep a size of its own (group resizing: hug/fill/fixed with min and max; size), from the Layout panel, the canvas handles and node.set; they were refused, and imported ones were dropped at the first relayout. Kept through main edits of other properties, rebuilds, undo, save, .fig and REST import (sizes a .fig instance gives its layers and the REST overrides fields), .fig export and detach; reset per group and with Reset all changes (instance_resizing_tests). The row that listed them as not overridable was wrong. Numbers unchanged. |
| 2026-10-14 |
minor |
Variables export in the browser: Export… ▸ Variables JSON / CSS custom properties did nothing in the web build, which has no save dialogs; it now hands the file over as a download (Services::downloads / download, variables_export_ui_tests). Import in the browser still needs a file picker, as do the other exports there. Numbers unchanged. |
| 2026-10-13 |
minor |
Component playground (M3): with an instance, a component or a component set selected, the Inspect tab shows a Playground: a preview of the component and a control for each variant, boolean and text property. What is tried there changes the preview and the code in the Code section, never the file (no undo step, the canvas instance stays as it is); Reset goes back to the component's or the instance's own values. component.playground {id?, properties?, language?, path?} gives the same to scripts: the properties with their values, the code, and a PNG on request (playground_tests). Left: instance-swap and slot properties in the playground (the command takes an instance swap; the panel has no control for it), a code connect template's snippet with the tried values. Numbers unchanged. |
| 2026-10-13 |
minor |
Variables inside styles (M3): a shared style can bind variables to fields of its value (style.bind {style, field, variable?}: a paint style's colours and gradient stops, an effect style's colour, radius, spread and offsets, a grid style's size, count, gutter and offset, a text style's font size, line height, letter spacing, family and style). Every layer using the style shows those fields resolved in its own modes and stays linked when a variable or a mode changes; the style keeps them resolved in the default modes. A style made from a layer keeps that layer's bindings; a new value drops bindings to what is gone. The style editor binds from its property rows (paint colour, shadow colour, blur radius, grid size) and detaches from the variable's chip; the style picker marks styles that use variables. .fig export and import carry the bindings on the style, REST layers hand theirs to the style they use. Left: binding text style fields and the other effect and grid fields from the editor (commands only), library variables in the picker, a style's library copy. Numbers unchanged. |
| 2026-10-12 |
minor |
Layers and Assets panels behaviour suite: Assets folders by page, frame and slash names; a leading . or _ keeps a component out of the library even after it was published, and removing it brings the component back |
| 2026-10-12 |
minor |
Extended collections' tail (gap 14): variables a library adds to a collection this file extends now count as a library update of their own (listed by name without a copy yet) and arrive inheriting when accepted, instead of waiting for the library's next other update; a library collection that is itself an extended collection can be extended again, values flowing down the chain as each file accepts updates with every level's overrides kept (checked end to end). Styles & variables breadth 94% → 95%, ready unchanged (76%), 3–5 h → 2–4 h; gap 14 1–2 h → 0–1 h (real-library checks left); feature breadth (~82%) and ready for real work (~51%) unchanged at this rounding; full parity ~305–594 h → ~304–593 h |
| 2026-10-11 |
minor |
Styles and variables behaviour suite from the public documentation (18 tests) and its fixes (styled properties edited away unlink, deleted variables unbind with values kept, type-checked bindings, alias checks on create, text styles on text only); style folders, order and duplicates with a Local styles list; mode duplicate and default; code syntax. Found missing: variables inside styles. Styles and variables breadth ~92% → ~94%, ready ~72% → ~76%; remaining here 7–14 h → 9–17 h |
| 2026-10-11 |
minor |
Behaviour suite from the public documentation and its fixes: overrides of padding, gap, alignment, layout grids, export settings, interactions and every text property kept (they were lost when the main changed); geometry, constraints and resizing of layers inside instances refused; variant switches keep only overrides both variants agreed on (text layers keep theirs); Alt-drops from Assets keep only text overrides; top-left default variant; hidden exposed instances hide their properties; push from nested layers goes to the outer main; variant property reordering and conflict warnings. Components ~95%/~68% → ~96%/~72%; remaining here 7–14 h → 5–11 h |
| 2026-10-11 |
minor |
Team libraries on figcraft-server (publish under the file, enable by a share link, updates with overrides kept), usage counts per component and file, and swapping a library by names (gap 10) |
| 2026-10-11 |
minor |
Extended collections, variables import/export (JSON, CSS) and gradient stop and variant property bindings landed: styles and variables breadth ~85% → ~92%, ready ~70% → ~72%; remaining here 10–20 h → 7–14 h |
| 2026-10-10 |
major |
Created from the 2026-10-10 re-measure |