Commit Graph
84 Commits
Author SHA1 Message Date
Dylan M. Taylor 23f57c8660 refactor(engine): simplify command handling for strict clippy 2026-10-28 16:00:36 +00:00
aiongg cea90cf935 refactor(engine): split cmd/edit.rs into edit/ 2026-10-26 20:17:48 +00:00
bflatastic e469cc8135 Merge main into #840
# Conflicts:
#	apps/figcraft-cli/src/files.rs
#	apps/figcraft-cli/src/main.rs
#	crates/engine/src/cmd/file.rs
#	crates/engine/src/lib.rs
#	crates/ui-egui/locales/en.toml
#	crates/ui-egui/locales/es.toml
#	crates/ui-egui/src/export_panel.rs
#	crates/ui-egui/src/export_preview_tests.rs
#	docs/file-format-parity.md
#	docs/mcp.md
#	docs/roadmap.md
#	docs/ui-parity.md
2026-10-26 23:23:07 +07:00
bflatastic 9c57ba1beb Merge main into #1202 2026-10-26 08:43:07 +07:00
BFlat 378ac56286 Merge pull request #1003 from kkilchrist/feat-994-batch-step-refs
command.batch: named step references (`as` / `$ref`) and paths in `field`
2026-10-26 08:31:53 +07:00
236f3185d6 Migrate FigCraft to egui 0.37 (#1312)
* E21: require release font coverage and stabilize capture deadline tests

* E21: separate capture, IME and field-save regression phases

* M0: correct the migration qualification reference

* M-egui037: document input lifecycle and UI regression contracts

* M0: correct egui 0.37 status docs and stabilize join-code regression

---------

Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
Co-authored-by: Codebuff <noreply@codebuff.com>
2026-10-25 15:04:39 -04:00
bflatastic dbf57d30c2 Merge main into #1003 2026-10-24 14:21:15 +07:00
BFlat 127247433a Merge pull request #1004 from kkilchrist/feat-1001-vector-path
node.set: accept `path` to replace a vector's geometry
2026-10-24 14:17:06 +07:00
bflatastic 13eba6a3ea Merge main into #1003
# Conflicts:
#	crates/engine/src/lib.rs
2026-10-24 12:53:57 +07:00
bflatastic ede3c634ad Merge main into #1004 2026-10-24 12:53:55 +07:00
bflatastic 6a172e4e52 Merge main into #1005 2026-10-24 12:53:53 +07:00
bflatastic bd80829a27 Batch r2-1 review: a paint keeps its own opacity through unbind and rebind
color_alpha is the part of a solid paint's opacity that is its typed colour's alpha, not a flag:
binding a colour variable takes only that part out, unbinding puts the variable's alpha back in,
and REST import records it. A 50% paint bound to a 50% variable stays at 25% bound or not.
2026-10-24 10:30:49 +07:00
bflatastic 62a3072134 Merge main and the community batch into #1202
# Conflicts:
#	crates/engine/src/component_codegen.rs
#	crates/engine/src/tools.rs
#	crates/ui-egui/locales/fi.toml
#	crates/ui-egui/locales/nl.toml
#	crates/ui-egui/src/dev_mode_tests.rs
#	crates/ui-egui/src/shortcuts.rs
#	docs/dev-mode-parity.md
#	docs/file-format-parity.md
#	docs/ui-parity.md
2026-10-24 09:39:52 +07:00
bflatastic 70536a6265 Merge #948: Keep a solid paint's own opacity when binding a colour variable (@Silvertree2010) 2026-10-24 09:21:42 +07:00
BFlat 8167f13a6a M12: Boards: a collaborative whiteboard file kind (#1229)
Board files (File > New board) with sticky notes, shapes with text, connectors that follow what they join (straight or elbow, arrowheads, labels), marker, highlighter and eraser, and stamps; a board toolbar and a contextual bar; the Design panel hidden on boards, Prototype and Dev Mode refused there. Boards are ordinary FigCraft files built from ordinary layers, so saving, undo, export, MCP and live sessions work unchanged. File tabs, recent files and Open files show a pen nib for design files and a note for boards (original icons drawn in code).
2026-10-24 04:18:55 +07:00
Nicek 261b8c5146 fix: 处理 PR 评审意见(剪贴板、图案填充、ArkUI 生成、资产缩略图)
粘贴其他应用的设计图层时保留已复制的填充/描边/效果行;粘贴重复图案前校验来源,拒绝自引用;ArkUI 生成补上变体枚举导出、对齐文本的宽度、auto layout 内绝对定位子节点,并用容器包裹带定位的 instance swap builder;资产列表只为可见行生成缩略图;Shift+Del 检查所有编辑图层的选中点;文档把 ArkUI 标为 FigCraft 自有能力并更新状态与修订历史。
2026-10-23 13:20:31 +08:00
KameronandClaude Opus 5.5 afdc31b727 Let node.set replace a vector's path
node.set took no `path`, so redrawing a vector meant deleting and recreating it and
losing its id, place in the layer list, paints, styles, bindings and step references.

`path` now takes SVG path data for vector layers and replaces the geometry through
the same reader node.create uses (path_network, shared with create). width/height in
the same call scale it as create does. The path's own box goes at the layer's current
origin, so rotation and flips stay; with x/y in the same call it goes at x/y plus the
box's offset, which is where create puts it.

On several layers, layers that aren't vectors are skipped with a warning, as auto
layout keys already are (the skip logic is shared now). A single non-vector is
refused, and so is a vector inside an instance: there is no geometry override, so
the next sync would undo it. Data that doesn't parse, draws nothing or has
non-finite bounds is refused cleanly; create now refuses non-finite bounds too.

Tests cover keeping the layer, placement and scaling against create, rotation,
undo/redo, invalid data, non-vectors, instances, boolean operands, edit mode, and a
junk-path sweep. The engine-wide junk sweep has node.set path entries.

Fixes #1001

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 11:21:24 -07:00
KameronandClaude Opus 5.5 9315e5e014 command.batch: named steps ($ref) and slash paths in field
A batch step can name itself with "as" and later steps refer to it with
{"$ref": "name"}, alongside positional {"$step": n}. "field" now takes a
slash path into the result as well as a key, so {"$ref": "copy",
"field": "ids/0"} reaches the first id edit.duplicate returned (the
convention variable.bind uses for fills/0/color; a leading "/" as in
JSON Pointer also works, and a key holding "/" still matches first).

Resolution moves to crates/engine/src/batch.rs (BatchStep, BatchScope),
shared by command.batch, the CLI script runner, the control channel and
the MCP batch tool, which now passes "as" through. Duplicate names fail
before any step runs; unknown, same-step or later names, bad paths and
out-of-range indexes fail the batch with the step, name and full path.
$ref is reserved like $step; nested batches keep their own names.

Fixes #994.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 11:19:47 -07:00
KameronandClaude Opus 5.5 3146ad2b2f node.reparent: index is the layer's final position (#992)
`node.reparent {ids, parent, index}` read `index` against the parent's
children as they were before the move, so within the same parent moving a
layer up one place did nothing and asking for two places moved it one.
`index` now counts the parent's children after the move, the way
`node.reorder` already does, in the same parent or a new one; several
layers still land together in their stacking order.

The layers panel was the only caller passing an index other than 0. It
drops on a gap between rows counted before the move, so it now subtracts
the dragged layers below that gap and drags behave as before.

Documents `index` in the command's params and in docs/mcp.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 11:03:08 -07:00
Nicek 12e0cdc147 feat: 为填充、描边和效果添加右键复制粘贴功能
实现了在属性行和分区标题上右键菜单,支持复制粘贴单个填充、描边和效果。新增了相关的命令、测试和国际化文本,完善了剪贴板数据结构以支持这些新功能。
2026-10-21 22:18:30 +08:00
NetterSilverandClaude Haiku 5.5 b788b55286 Keep a solid paint's own opacity when binding a colour variable
Fixes #923

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:43:51 +02:00
bflatastic b82369201d M3: duplicating a main component makes an instance (#892)
Duplicate and Alt-drag copied a main component as a second main. Within
its file the copy is now an instance of it (components::copy_in_file,
shared by edit.duplicate and the Alt-drag copy), as Copy then Paste
already was. A variant duplicated in its set stays a new variant.
2026-10-20 05:44:46 +07:00
bflatastic f22e2ed825 Merge main into #870 2026-10-20 02:04:33 +07:00
BFlat 3ed4578112 Merge pull request #851 from MortenHusted/fix/effect-unknown-keys
M3: effects refuse keys they don't read instead of dropping them
2026-10-20 00:46:45 +07:00
BFlat 409de1a762 Merge pull request #871 from justinjohn0306/fix/809-scalar-bound-edits
M3: explicit edits of bound scalar fields detach their variable (#809)
2026-10-20 00:10:09 +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
MortenHustedandCodex 0108a7c2f5 M3: copied components paste as instances within their file
Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
2026-10-19 13:34:40 +02:00
MortenHustedandClaude Opus 5.5 6f90717e71 M3: effects refuse keys they don't read instead of dropping them
node.set refuses unknown properties, but the effects inside it were read
by serde, which drops keys it doesn't know. A drop shadow given
offsetX/offsetY (the names variable bindings use for its offset) was
stored with the default offset [0, 4] and the call said ok. effect.add
and effect.set dropped them the same way.

The three now refuse what the effect doesn't read and say what it
takes: "effects[0]: unknown properties: offsetX, offsetY (a dropShadow
takes blend, color, offset, radius, showBehind, spread, type, visible)".
A key counts as read when setting it to null makes the effect fail to
parse or come out different, so serde's aliases count as read without a
list of them here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:53:05 +02:00
Filip Strajnar eae9546878 M6: export as Android Vector Drawable (XML)
ExportFormat::VectorDrawable ("XML", .xml) and ExportSetting.vd_compat
(bestEffort / exact / ignore, stored only when not the default) wire the
figcraft-vectordrawable writer into file.export, file.exportLayers and
export.add/update: a vdCompat param (junk refused), svgIdAttribute writes
android:name, and the results carry the writer's warnings (per file for
export layers). The Export section lists XML with an "Android Vector
Drawable" tooltip and its id attribute and compatibility options, File >
Export... gets an XML preset, and the toasts say when content was changed
or left out. CLI: --format xml / .xml outputs, --vd-compat, warnings as
note: lines. MCP: format xml returns the XML as text plus its warnings.
A .fig export writes XML settings as SVG with a note.

Docs: file-format-parity (row and revision), ui-parity and roadmap
revisions, architecture (the crate), cli and mcp.
2026-10-18 20:39:46 +02:00
MortenHustedandClaude Opus 5.5 c7261cb907 M3: an explicit paint edit detaches the variable bound to what it changed
fill.set, stroke.set and node.set fill/fills/stroke/strokes reported ok
while normalize put the bound colour straight back, and node.set
{fills: []} brought the bound paint back. They now drop a paint's
colour, opacity or stop binding where the edit changed that value, as
selection.setPaint already did (its comparison moves to
lists::rebind_edited_paint, which also lets stop bindings follow a
removed stop). boundVariables applies last in props::apply, so a
binding given with the paints in one call stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 14:14:38 +02:00
skylar taylor-barrick c7acd1a81d chore: clear the rustc 1.96 clippy lints that fail xtask ci
Unrelated to the security changes in this branch; `main` already failed
`clippy --workspace --all-targets -- -D warnings` without them. Eight
clippy::nonminimal_bool sites across seven files, each taking clippy's own
suggested rewrite.

Every rewrite is pure boolean algebra with the negation kept outside, so the
float comparisons and their NaN behaviour are untouched: !A && !B becomes
!(A || B), and !opt.is_some_and(f) becomes opt.is_none_or(!f). Two sites needed
explicit parentheses the suggestion omitted, where the rewritten term is the left
operand of an && and || binds looser.

designcraft cleared the same lint in bc840130; this is figcraft's equivalent.
2026-10-17 13:35:44 -06:00
bflatastic adde034a7c Merge main into #603 2026-10-17 19:40:01 +07:00
bflatastic 99d097636e Merge fix/all-f5 into batch 12 2026-10-17 16:59:57 +07:00
bflatastic e912a2efe8 #655: fill.set / stroke.set read index with params::opt_index (#675) 2026-10-17 16:38:35 +07:00
bflatastic 6f8c2a657e Merge fix/680 2026-10-17 16:37:27 +07:00
bflatastic 01ce23ca76 #680: page refusal names the calling command (edit.copy / edit.cut) 2026-10-17 15:58:23 +07:00
bflatastic 3353d2d5d3 #647: edit.pasteDesignLayers, named neutrally; design data capped at 256 MB before decoding, data accepts wrapped or unpadded base64 2026-10-17 14:34:57 +07:00
NetterSilverandClaude Haiku 5.5 0e25cf4c26 Refuse pages in edit.copy and edit.cut
Found while testing crates/engine/src/cmd/edit.rs:499 (copy_to_clipboard, called from copy at :536 and cut at :542)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 13:47:07 +00:00
NetterSilverandClaude Haiku 5.5 a911ab9614 Reject negative or fractional index in fill.set and stroke.set
Found while testing crates/engine/src/cmd/edit.rs:1180

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 08:29:55 +00:00
JimKnoxxandClaude Opus 5.5 ab10e37d43 M1: paste layers copied in Figma
Figma puts copied layers in the clipboard's HTML as a base64 .fig canvas
between (figma) markers. edit.pasteFromFigma reads it with the .fig import,
turns the layers into the session clipboard and pastes them as edit.paste
does (selected frame, in place or view centre), with the import's warnings.
The desktop app reads the clipboard's HTML (arboard) and Paste prefers a
Figma copy; the browser's paste event carries plain text only.

Tested on copies written by our own .fig export; not yet on real copies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 08:20:49 +02:00
Edivan TeixeiraandClaude Opus 5.5 8cccc48516 node.set: apply auto layout keys to the frames and name the rest; resolve fontStyle like text does
- Several ids: auto layout keys go to the frame-like layers, the others get the remaining keys
  and are named in `warnings`. Refused only when no target can have auto layout.
- fontStyle is never refused: it draws in the nearest face, as a text layer resolves it.
  `warnings` says so when that face's weight or slant differs from what the name asks for.
- set_properties passes `warnings` on to the agent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:43:52 -03:00
bflatastic db8bf6ba33 Merge main into #560; normalise the replacement where findReplace reads it
Main's case-insensitive replace loop (from an earlier PR) took the place of the
loop this change edited, so the replacement text is normalised once, where
the parameter is read.
2026-10-16 02:31:34 +07:00
MarekSpodBiedry 843b00b22f text: scripted text stores CRLF, CR, U+2028 and U+2029 as LF like the editor (#427) 2026-10-15 17:20:58 +02:00
bflatastic a95f95ae37 #361: a replacement that only changes the case replaces every occurrence; the text's chars are collected once 2026-10-15 18:54:50 +07:00
bflatastic 6215cae877 Merge main and the community batch into #361
# Conflicts:
#	crates/engine/src/cmd/edit.rs
2026-10-15 18:52:02 +07:00
bflatastic 3c52dad774 Merge #353: engine: long digit strings no longer overflow object.opacityKeys (@midasmatterhorn) 2026-10-15 17:54:23 +07:00
bflatastic 5a57388b8d Merge #344: doc: name counters stop at u64::MAX instead of overflowing (@midasmatterhorn) 2026-10-15 17:54:21 +07:00
bflatastic 022821211f Merge #358: Replace every occurrence in edit.findReplace when replacement contains find (@Silvertree2010) 2026-10-15 17:53:06 +07:00
bflatastic 03716da731 Merge #384: Reject negative or fractional index in effect.set, export.update and export.remove (@Silvertree2010) 2026-10-15 17:53:04 +07:00
NetterSilverandClaude Haiku 5.5 87b9a3a9ce Reject negative or fractional index in effect.set, export.update and export.remove
Found while testing crates/engine/src/cmd/edit.rs:256 (effect.set), :305 (export.update), :317 (export.remove)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 01:08:36 +00:00