Commit Graph
29 Commits
Author SHA1 Message Date
bflatastic b29e17f5c0 Merge main into #842
# Conflicts:
#	crates/engine/src/variables_behaviour_tests.rs
2026-10-20 00:25:18 +07:00
BFlat 7ca8c80a8f Merge pull request #814 from muamerETS/fix/layout-remove-gap-binding
M3: removing auto layout drops its gap and padding bindings (#810)
2026-10-20 00:23:28 +07:00
justinjohn0306andClaude Opus 5.5 6a163721dc M3: explicit edits of bound scalar fields detach their variable (#809)
An explicit edit of a bound width, height, opacity, gap, padding, corner
radius, stroke weight, arc or text field reported ok, but normalize wrote
the variable's value straight back. Now the edit sticks and that field's
binding is dropped, as paint edits already do.

- `styles::bound_before` / `detach_edited`: compare each layer-level
  bound field's stored value before and after the edit (`bound_field`),
  so keys written unchanged keep their bindings and fields moved along
  with another (proportions, min/max) lose theirs. A binding the edit
  itself made (`boundVariables`) stays. List and component property
  bindings are left to their own rules.
- Hooked into node.set, transform.resize, object.opacityKeys, text.style
  and `Session::preview` (canvas drags of the selected layers).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 17:33:29 +05:30
Muamer MustaficandCursor 79ac16a256 M3: doc comments for the structure commands and binding re-apply (#810)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-18 23:40:35 +02:00
NetterSilverandClaude Haiku 5.5 39f30d63c8 Refuse empty mode lists and blank names in variable.createCollection
Found while testing crates/engine/src/cmd/styles.rs:119-124

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 21:27:31 +02:00
Muamer MustaficandCursor 4506f49ad4 M3: removing auto layout drops its gap and padding bindings (#810)
layout.remove and node.set {layout: null|"none"} now drop the bindings of auto layout fields (gap, crossGap, padding...), as frame-to-section already did for some of them (it now drops all of them). Re-applying a binding no longer creates an auto layout on a frame without one: the binding waits for the next auto layout.

Closes #810

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-18 15:21:28 +02:00
BFlat 018b8166b2 M3: a bound colour variable's alpha applies once, not squared (#802) (#804)
Fixes #802.

Fixes #802.
2026-10-18 18:50:19 +07:00
Skyfuller1303 4b29d8c34d M1: apply instance overrides by key and order, settle bound paints, keep stroke outline styles
Override rows of an instance can name a layer of its main by the key the layer
had before the main was edited. The key now resolves to the layer's current id
when it is unambiguous and the current id does not exist already. Rows of an
enclosing instance win over those of the instances nested in it, whatever order
the wire lists them in, and an explicit "no style" on an override detaches the
style instead of falling back to the older field.

Paints bound to variables take the colours of the active modes when a document
loads, before the first edit, without touching other pending changes.

A stroke-only vector that imports as the outline of its stroke now moves the
stroke's shared style and colour variables to the fill it becomes, and vectors
whose single segments carry their own stroke style say so in a warning.
2026-10-16 03:32:38 +02:00
MarekSpodBiedry fdf397d7dc Refuse to create a variable collection with a name that is already taken (#416) 2026-10-15 17:17:44 +02:00
bflatastic a399ff8934 M3: variables inside styles 2026-10-13 13:36:54 +07:00
bflatastic f3012ce6e4 M3: styles and variables behaviour suite and fixes
**A styles and variables behaviour suite written from the public documentation, and fixes for every divergence it found.** Each documented rule (what a style holds, creating from a selection, applying, editing, detaching, deleting a used style, organising by folders and order, descriptions; variable types, modes and the default mode, aliases, scopes, mode inheritance, bindings by field and type, deleting a bound variable, code syntax, number variables in pixels) is a table-driven case with a one-line derivation; where the documentation is silent the case says so and pins the behaviour.

**Fixes (10 of the suite's 18 tests failed on the old code, counting the missing settings below)**
- **Editing a styled property unlinks that style**: a fill, stroke, effect or layout grid list edited away from its style (through `node.set`, a paint row, an added fill, or a variable bound to it) drops the link, as text chars already did. Before, the layer kept a stale link and the next style edit silently overwrote the user's edit. Moving or renaming a layer keeps its links.
- **Applying a style replaces variables bound to what it sets** (the fill list for a paint style, the typography for a text style); other bindings stay. Before, the variable won again on the next edit.
- **Text styles go on text layers only**: applying one to a mixed selection leaves the other layers alone (they used to get a stray text style link); a selection without text is refused.
- **Deleting a bound variable unbinds the layers using it**: they keep the value they show; variables aliasing it take its values per mode (docs silent on aliases). Before, the binding dangled. `variable.deleteCollection` now does the same for aliases from other collections into it.
- **Bindings are type-checked**: a variable of a type the field doesn't take (a colour on `width`, a number on `visible`) is refused; scopes still only filter pickers. Number variables can fill text content (shown as text).
- **Aliases are checked when a variable is created** (`variable.create {value: {alias}}` took any name, of any type).
- **Style names** are trimmed per `/` folder part; empty names are refused.

**Missing documented settings, added**
- **Organising styles**: `style.move {styles, before?, folder?}` reorders and moves into a folder (renaming the style to match), `style.duplicate` puts "<name> copy" right below the original, `style.renameFolder` renames a folder's styles. The page's **Local styles** list (nothing selected) is now grouped by type and folder with swatches and text sizes: click opens the style editor, right-click offers Edit style / Duplicate / Delete, a row dragged by its grip moves (into the folder it lands in), right-click on a folder renames it.
- **Modes**: Duplicate mode (`variable.addMode {from}`), Set as default and Move column left/right (`variable.moveMode {collection, mode, to}`); layers on Auto follow a new default, layers set to a mode keep theirs.
- **Code syntax** per platform (Web, iOS, Android): `variable.update {codeSyntax}`, three fields in Edit variable, carried by the variables JSON (import and export); generated CSS and the CSS variables export use the Web name (`var(--x)` or `--x`).
- **Number variable scopes**: font weight, paragraph spacing, paragraph indent and text content (and `fontWeight`, `paragraphSpacing`, `paragraphIndent` bind with their scopes).

New strings go through `tr(...)` with `en.toml` / `es.toml` entries, new commands have Spanish labels; the new rows, fields and menus have accessible names. Each edit stays one undo step.
2026-10-12 00:57:43 +07:00
BFlat ee82cdd138 M3: extended variable collections, variables import/export, gradient stop and variant bindings (#114)
Library collections can be extended in a consuming file (inherited modes, per-mode overrides with reset, extra variables; library updates keep overrides). Variables export to the REST JSON shape or CSS custom properties and import back (merged by name, validated by the REST reader, one undo step). Gradient stops and variant properties bind to variables. Also fixes clicking a gradient stop adding a new stop.
2026-10-11 10:49:16 +07:00
BFlat 08d91869cb M4: live boolean editing, arc fields, shape builder on strokes, one-sided widths (#111)
Boolean operands are editable in place with live results (per-operand operations, cached prefixes so a drag recomputes from the edited operand up); a Design panel Arc section; the shape builder takes strokes and open paths as outlined areas; variable width points can be wider on one side.
2026-10-11 10:04:25 +07:00
BFlatandClaude Opus 5.5 1444754f66 M9: collaboration polish: unique ids for styles, variables and comments; live text carets (#107)
- Styles, variables, collections, modes and comment threads created in a live
  session take ids from the creator's client space (figcraft_doc::ids: a
  number from the node id allocator, client_id << 32 | counter); outside a
  session ids are made exactly as before. The server refuses a new entry in
  another connected client's space (removed entries can be put back by undo),
  keeps removed entries in its saved state, and starts client ids above every
  space used by any kind of id.
- Others' text carets and selected characters show on the canvas in their
  colour at the glyph positions, with their name above the caret (no blinking,
  no frames of their own). Typing in a layer someone else is typing in says so
  (toast and tag); collab.peers reports who types where.
- Toasts wrap at a readable width.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:29:30 +07:00
BFlatandClaude Opus 5.5 3c63e6d7c8 M3: components at scale; Alt-drag to swap instances (#91)
* M3: components at scale; Alt-drag to swap instances

- Synthetic design system (engine `synthetic_ds`): ~300 components (icons; button, input,
  card, nav and badge sets of size x state x type x theme variants), boolean/text/instance
  swap/slot properties, instances nested four deep, text and paint styles, light/dark
  variables bound across it, and 2,000 instances with overrides at every level.
- Propagation: value, size and added/removed-layer edits of mains reach their copies
  directly; other edits rebuild each affected instance once, inner mains first; rebuilds keep
  unchanged layers shared; bound variables no longer touch unchanged layers; auto layout
  re-flows only where sizes or places changed.
- Fixes: values from variables (theme switches, bound radii) are no longer recorded as
  overrides; a detached variable on an instance layer is an override that survives main
  edits; exposed nested instances stay exposed; swapped nested instances keep their layer
  names and the ids/overrides of layers their own main adds; renamed nested instances keep
  their name when detached out.
- Instance swap properties have preferred values. `instance.swapAt` / `library.swapAt` and
  Alt-drop from the Assets panel onto an instance swap it (highlighted while dragging).
- Instances pasted into other files follow their mains (library copies shared, published
  components used from their library, other mains left behind as a missing main).
- Assets panel lays out only visible tiles and renders thumbnails lazily with a cache
  invalidated per changed component; instance panel lists components once per revision.
- `figcraft-cli perf --instances`, UI frame bench and tests in components_scale_tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* M3: the swap perf case drops on a glyph the hit test reaches

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Merge origin/main into feat/components-scale; weighted depth 79%

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:07:43 +07:00
BFlatandClaude Opus 5.5 70b2666c90 M3: progressive blur, inner shadows under strokes, noise and texture in SVG (#86)
- Inner shadows paint over the fills and under the children and strokes, as documented (canvas,
  PNG, PDF and SVG alike; text fills every run before the inner shadow and the strokes).
- Layer and background blur gain a progressive type: the radius ramps linearly from a start radius
  at a start point to the end radius at an end point (unit-box points, like gradient handles).
  Rendered by blending a few geometric blur levels, each blurred only around the pixels that use
  it; bands and partial renders match one piece. Design panel type menu and Start/End fields,
  on-canvas handles while the settings are open, effect.add/effect.set params, startRadius
  variable bindings, scale tool, REST import (blurType, startRadius, startOffset, endOffset),
  Inspect row; CSS leaves progressive blurs out.
- SVG export embeds renders for what filters can't match: noise grains over the vector layer
  (crisp, blended), texture and progressive layer blur whole, background blur and glass from what
  lies under the layer among the exported layers. PDF and SVG share the renderer's helpers.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 17:20:07 +07:00
BFlatandClaude Opus 5.5 4565c4ab93 M3: reorder paints, effects and layout grids in the Design panel; grid and instance property variable bindings (#81)
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>
2026-10-10 15:21:05 +07:00
BFlatandClaude Opus 5.5 009314662c M3: variable bindings follow paint and effect lists (#73)
A bound fill or stroke colour now sets only the first paint's colour, keeping its opacity,
visibility and blend mode and the rest of the list (it used to replace every fill with one solid
paint). Effect bindings (`effects/<i>/<field>`) follow their effect when an effect is removed or
moved with the new `effect.move {ids?, from, to}`; removing a bound effect or the first fill/stroke
drops its bindings instead of passing them to a neighbour. `effect.remove` rejects negative
indices.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:17:09 +07:00
BFlatandClaude Opus 5.5 340b673666 M3: text depth: RTL names, text style browser, axis previews (#72)
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>
2026-10-10 13:03:09 +07:00
BFlatandClaude Opus 5.5 b2738d8f0b M5: prototype device rotation, presentation scaling, scroll momentum, mode picker (#62)
- 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>
2026-10-10 09:48:29 +07:00
BFlatandClaude Opus 5.5 d7fd41cd13 M3: library variables (#60)
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>
2026-10-10 09:06:18 +07:00
BFlatandClaude Opus 5.5 ec5da02a35 M3: bind effects to variables (#55)
Shadow X, Y, blur, spread and colour, and layer/background blur radius
take variables from the Effects section like other fields (variable
button on hover, a chip to change or detach once bound). Bindings are
stored as `effects/<index>/<offsetX|offsetY|radius|spread|color>` and
applied in normalisation; pickers offer effect-scoped variables.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 07:52:38 +07:00
BFlatandClaude Opus 5.5 360b6df206 M4: local libraries: publish, enable, place and apply, updates with overrides kept (#32)
- 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>
2026-10-10 05:24:46 +07:00
BFlatandClaude Opus 5.5 1c6f04bf62 M3: Variables editor with aliases, scopes, and rename/delete for variables, modes and collections (#33)
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>
2026-10-10 05:12:08 +07:00
BFlatandClaude Opus 5.5 cb8151c999 M3: bind variables to opacity, padding, gaps, sizes, font size, text and visibility (#31)
Number fields for opacity (variables hold percentages), individual corner
radii, auto layout padding and gaps, min/max sizes and font size now take
variables like width and corner radius already did. Text content binds to
string variables and visibility to boolean ones from the Typography and
Appearance section headers; bound ones show as a variable chip that
changes or detaches the binding.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:57:36 +07:00
BFlatandClaude Opus 5.5 d30ee21566 M1: paint depth: gradient stops on the canvas, colour models, colours on this page (#7)
- Gradient stops on the canvas: drag a stop along the gradient line, click the line to add a
  stop (sampled colour), the selected stop is ringed and shared with the picker; Delete removes
  it (two always stay).
- Colour picker value row: a model dropdown (Hex, RGB, HSL, HSB, CSS) with its fields and the
  alpha field; scrubbing works on the numeric fields.
- "On this page" swatches: the page's most used solid colours (cached per document snapshot);
  swatches set a solid colour or the selected gradient stop.
- New command `paint.documentColors {page?, limit?}`; control channel `ui.set {colorPicker}`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 02:57:40 +07:00
Brandon ThomasandClaude Opus 5.5 b26b449fe3 Fixes from agent reports: hex colours everywhere, camelCase in/out, unique variable ids, strict batch, ui.key modifiers, export rounding, Fit images, inner shadows on all edges
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:34:25 -04:00
Brandon ThomasandClaude Opus 5.5 b36fd771ab M0.9: MCP server + figcraft-cli, PDF export, web app, release/CI packaging, vector edit mode, workspace lint cleanup
- crates/mcp: JSON-RPC MCP server (18 tools, resources, prompts), headless and attached backends, end-to-end agent tests
- apps/figcraft-cli: info/export/convert/render/run/commands/mcp/perf
- crates/pdf: vector PDF export via krilla with embedded text; file.exportFramesPdf
- apps/figcraft-web + packaging/{macos,windows,linux,freebsd,web} + release/ci workflows
- engine vectoredit: edit points/handles/bend/add/delete-heal; canvas overlay and edit strip

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:31:53 -04:00
Brandon ThomasandClaude Opus 5.5 56a312ba5f M0.5: layout (auto layout + constraints), format, svg, engine (commands, history, tools, components, styles, variables)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:40:18 -04:00