92 Commits
Author SHA1 Message Date
JonRC a55a728c4c M6: JPG exports take a quality and embed the sRGB profile (#2004)
file.export always encoded JPG at quality 90, and an sRGB document's JPGs
carried no colour profile, so viewers could only assume sRGB. PNG exports
always get an sRGB chunk, and Display P3 JPGs already embedded their ICC
profile.

- ExportSetting gets a quality (1 to 100, default 90, saved only when it
  isn't 90), parsed with the other export options: file.export, export.add,
  export.update, file.exportLayers and MCP export take it.
- encode_export_with takes the quality; encode_export keeps quality 90.
  Every JPG now embeds the document's profile, sRGB included.
2026-11-05 00:09:19 -03:00
Dylan M. Taylor f54e099a48 fix(mcp): render prompt placeholders in one pass over the template (#1922)
Each argument was substituted into the already rendered text, so a value
holding placeholders was expanded again by every later replacement and a
short request could grow the prompt without bound. The template is now
scanned once and argument text is never read as syntax.
2026-11-03 22:27:10 -05:00
BFlat 3b77c236af Merge pull request #1731 from zral/docs/vector-path-xy-offset
M0.9: document that x/y offset a vector path by its own box
2026-10-31 09:14:25 +07:00
BFlat 437d052396 M8: a control request can set its own timeout, up to 10 minutes (#1696) (#1706)
A request line can carry timeoutMs beside id and method, capped at 600,000 ms; without it the wait stays 60 s and older apps ignore the field. figcraft-cli mcp --attach PORT --timeout SECONDS sends it with every request and reads the answers that much longer. A timeout's message says how long the request waited.
2026-10-31 03:41:56 +07:00
Lars SøraasandClaude Opus 5.5 57f0185f41 M0.9: document that x/y offset a vector path by its own box
node.create / vector.create (and MCP create_node) place a vector made from
SVG path data or points at x/y plus the box's unscaled offset: a path whose
box starts at (30, 0) with x 830 lands at 860. That is deliberate and pinned
by vector_path_set_tests, but the MCP schema described x as the layer's left,
which misled an agent drawing a poster. The x, y and path descriptions and
the node.create / vector.create parameter docs now say so, and an MCP test
pins the placement for path and points, the 0,0 alternative, set_properties
x moving the left edge, and the documentation.

CI: fmt, clippy, layers, assets and wasm pass; every test passes except
board_tables_tests::a_double_click_types_in_the_cell_and_its_row_grows_with_the_text
and clipboard_fence_tests::a_synthetic_copy_shortcut_leaves_the_system_clipboard_alone,
which fail the same way on main without this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 18:12:20 +01:00
BFlat d17de284da Tests: read the docs' examples whatever the checkout's line endings (#1645)
inspect_fields::compact_examples_run_verbatim_from_the_docs split docs/mcp.md and docs/control-protocol.md on text ending in a newline, so on a Windows checkout with core.autocrlf (CRLF docs) it panicked and cargo xtask ci failed locally. The test now reads both with LF line ends. Found while gating on Windows.
2026-10-30 15:53:33 +07:00
Mert Dede 5c3aef840a Merge remote-tracking branch 'origin/main' into feat/996-inspect-fields
# Conflicts:
#	docs/roadmap.md
2026-10-30 07:07:54 +03:00
BFlat bc11197c21 M1: Enter or a double-click with the pen edits the vector with the Move tool (#1430) (#1590)
A double-click with the pen drew on: its second press added a point on the last one, and a double-click meant to edit a layer drew a stray vector on it. A press on the last anchor now adds nothing; a double-click ends the path (or, when it only started one, edits the layer under it) and Enter ends the path, both going on in vector edit mode with the Move tool. In edit mode the pen's Enter and double-click take the Move tool, the double-click taking back what its first press began. Escape still keeps the pen (#760).
2026-10-30 08:41:01 +07:00
Mert DedeandClaude Opus 5.5 42e1a39d72 Merge main into #1420
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 01:47:34 +03:00
BFlat 3af5d8a534 M9: comment.list reports the names each message mentions (#1415) (#1534)
Every message (the first and each reply) gets `mentions`: {name, known,
you} for each @mention, by the rule the thread view highlights with
(figcraft_doc::mentions, now shared with mention_ranges). Names resolve
against the file's people and you; unknown names are kept with
known: false; a@b.com mentions nobody.
2026-10-29 22:07:06 +07:00
BFlat 3108d5bee7 M8: edit.duplicate returns an id map with map: true (#995) (#1528)
With map: true, edit.duplicate also returns map: each original layer's id, descendants included (nested frames, groups, instances and their sublayers), paired with its copy's id, so a batch can edit inside a copy through a step reference instead of inspecting it. Without map the result is unchanged.
2026-10-29 20:34:30 +07:00
BFlat 714bbfcdaa M8: MCP and control calls name the document they act on (#998) (#1533)
Tools that act on a document, and the control methods engine.execute,
document.inspect, node.get and ui.render, take an optional `document`
(an open file's id from doc.list / document.inspect, or its path). The
call acts on that file and the tab in front stays in front, so an
attached agent keeps editing its own file when the user switches tabs.
2026-10-29 19:47:41 +07:00
BFlat 72b4269383 M8: an AI agent attached over MCP comments under its own name (#1414) (#1529)
Every command an MCP client runs acts as the client's name from initialize (clientInfo.title, else .name) with ' (agent)' after it, or 'AI agent' without one, carried to the app as engine.execute {agent}. Its messages, reactions and read state are its own, and its messages are marked (agent: true in the file and comment.list, an Agent label in the thread). The user's name and comments made in the app are unchanged; in live sessions the name and marker travel with the thread.
2026-10-29 19:15:19 +07:00
dd55ef63dd Merge main into #1420
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
2026-10-29 12:18:33 +03:00
bflatastic e89def2068 Merge main into #1409 2026-10-29 13:46:42 +07:00
BFlat c2c720087c Merge pull request #1408 from avhn/feat/996-node-find
M0.9: find layer ids without changing the selection (#996)
2026-10-29 13:41:43 +07:00
BFlat 3be354d227 Merge pull request #1461 from MortenHusted/fix/open-file-reload
M1: opening a file that is already open says when it changed on disk, and can read it again (#997)
2026-10-29 13:41:21 +07:00
bflatastic c6c11471db Merge main into #1408
# Conflicts:
#	crates/engine/src/cmd/edit.rs
#	docs/roadmap.md
2026-10-29 12:23:48 +07:00
bflatastic c5ba6dd2e8 Merge main into #1409
# Conflicts:
#	crates/engine/src/session/undo.rs
#	docs/roadmap.md
2026-10-29 12:23:11 +07:00
bflatastic d1e96ea706 Merge main into #1461 2026-10-29 12:22:30 +07:00
bflatastic a51e815073 Merge main into #1460 2026-10-29 12:22:28 +07:00
bflatastic 49a41cddd4 Merge main into #1408
# Conflicts:
#	docs/roadmap.md
2026-10-29 11:40:48 +07:00
bflatastic 1505681a54 Merge main into #1407 2026-10-29 11:39:50 +07:00
bflatastic 56196eac5a Merge main into #1409
# Conflicts:
#	ROADMAP.md
#	crates/engine/src/session/undo.rs
#	docs/roadmap.md
2026-10-29 11:27:45 +07:00
bflatastic f154011299 Merge main into #1461 2026-10-29 11:19:05 +07:00
bflatastic f99f01d57b Merge main into #1460 2026-10-29 11:09:30 +07:00
bflatastic fd81433a23 #1460: text.style lists the text-decoration and variation keys the Typography panel sends, paragraph spacing on selected characters goes to node.set, MCP builds its tool catalogue once, and a test for the params panels build 2026-10-29 11:09:09 +07:00
phonedu 8e6b3bf449 M12: MCP comment feedback and live change events
Comments become the task queue for an attached or headless MCP client: comment.prompt returns a bounded Markdown brief, the Comments panel gains Copy for AI, and the MCP server exposes fix_comments, watch_comments, figcraft://comments and wait_for_changes. Mutating comment.* commands append to a bounded in-memory event log; the control channel's events.poll long-polls it without the UI thread, and a successful batch commits its events only if the whole batch lands. No model, network dependency, asset or file-format change.
2026-10-28 21:42:31 +08:00
MortenHustedandClaude Opus 5.5 3567bef862 #1460: commands and tools that pass their params on list what they pass
file.placeImage runs image.place's code and file.exportSvg file.export's,
so their docs now list the names those read (width, height, target, mode;
scale or size and the export options). MCP's create_node passes points,
closed and select to node.create, and pointer and key read ctrl, so their
schemas list them too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:35:23 +01:00
MortenHustedandClaude Opus 5.5 40b9288f70 M1: opening a file that is already open says when it changed on disk, and can read it again
`file.open` on a path already open in a tab switched to it and returned
its index, even when another process had changed the file since, so the
caller kept looking at the old copy with nothing saying so (#997). The
tab's disk stamp from #1330 (`DocState::on_disk`) now tells:

- the result says `stale: true` when the file on disk differs from what
  the tab read or last saved;
- `reload: true` reads the file again into the tab, as Revert to saved
  does (refused while the tab has unsaved changes or is in a live
  session), and the result says `reloaded: true`.

MCP's `open_file` takes `reload` and passes it on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:19:56 +01:00
MortenHustedandClaude Opus 5.5 27c11858e1 M0.9: MCP tools and batch steps refuse names they don't take (#1413)
`run_command {"command": "inspect.code", "args": {...}}` ran the command
without its params, on the selection. MCP tool arguments are now checked
against each tool's input schema (and the objects it describes inside,
such as a batch's steps; open objects like `params` take anything), and
a step key other than `command`, `params` and `as` is refused in
command.batch, figcraft-cli scripts and the MCP batch tool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:06:59 +01:00
d86a7ad9d9 M0.9: inspect layer trees with selected fields or a compact outline (#996)
node.get, the control channel's inspect methods and the MCP get_node / inspect_document tools
take fields: [node keys], applied at every level, or outline: true (id, name, type, characters
cut to 120 characters, mainComponentName, visible). Ids and structure are always kept, properties
that aren't selected aren't computed, and the default output is unchanged. Part of #996.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
2026-10-27 12:55:47 +03:00
300a5f2f9d M0.9: let batches continue after failed steps (#1386)
command.batch and the MCP batch tool take onError: "abort" (the default, unchanged) or
"continue". In continue mode each step runs inside a rollback-only checkpoint split out of
Session::grouped: a failed step is rolled back on its own, later steps still run, and the result
is one {step, command, ok, result|error} object per step. What was applied is one undo step; a
reference to a failed step fails too; malformed batches are still rejected before any step runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
2026-10-27 07:57:54 +03:00
57c1133a34 M0.9: find layer ids without changing the selection (#996)
node.find {root?, name?, type?, text?, component?, limit?} returns {ids, total} for the strict
descendants of root (or the current page) that match every filter given: name, text and main
component name as case-insensitive substrings, type as an exact type name. It changes neither the
selection, the page nor the undo history. Part of #996; fields/outline are left for later.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
2026-10-27 07:57:54 +03:00
8ae76d9b2a M8: bound attached MCP replies to 512 MiB (#1113)
The attached backend read each reply from the app's control port with read_line, which has no
limit. It now reads up to 512 MiB before the newline (an app window capture is at most 256 MiB as
base64) and stops at the first byte past that. An oversized or non-UTF-8 reply drops the
connection and is reported like a lost reply; the next call reconnects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
2026-10-27 07:57:53 +03:00
BFlat cac54fd552 Merge pull request #840 from StrajnarFilip/feat/vector-drawable-export
M6: export as Android Vector Drawable (XML)
2026-10-27 01:06:21 +07:00
bflatastic de826e14cf Merge main into #650
# Conflicts:
#	crates/ui-egui/locales/es.toml
#	crates/ui-egui/locales/pl.toml
#	docs/prototyping-parity.md
2026-10-26 23:24:22 +07:00
bflatastic c208ae442c #840: move the CLI and MCP XML export tests into tests/it 2026-10-26 23:23:08 +07: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 07a654959a Merge main into #1021
# Conflicts:
#	docs/file-format-parity.md
2026-10-26 09:12:39 +07:00
BFlat 09d2d96588 Merge pull request #1353 from storytold/fix/sonar-new-code-refactor
refactor: clear SonarCloud new-code issues (S3776, S107) and exclude generated coverage tree
2026-10-26 09:04:36 +07:00
Dylan M. Taylor 7dc5d3b377 refactor(mcp): group with_marker's cut sizes into a struct (SonarCloud S107)
The depth and wire-size numbers describing a cut subtree are now one Cut
value, so with_marker takes 5 parameters instead of 8. Output is unchanged.
2026-10-25 21:11:43 -04:00
Jindřich ŠaršonandClaude Opus 5.5 b8c120f30c M6: Sketch import: name .sketch in the file.open, file.import and MCP open_file docs
The command parameter docs (CLI `commands`, MCP list_commands, the Actions
palette) and the MCP open_file tool description listed .fig but not
.sketch; docs/mcp.md and docs/cli.md already did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:33:22 +01:00
bflatastic dbf57d30c2 Merge main into #1003 2026-10-24 14:21:15 +07:00
JimKnoxxandClaude Opus 5.5 545cd630fd M5: overlay positions relative to the layer that opens them
From the Figma forum request "Set overlay position relative to element,
not to page": an open overlay action takes anchor: screen (default) |
triggerInside | triggerOutside. With a trigger anchor the position preset
is measured on the layer that opens the overlay instead of the screen.
Outside puts top presets above it and bottom presets below it with the
edges lined up (a dropdown is Bottom left + Outside); Center centres it
on that layer. Manual is unchanged.

The interaction editor gets Relative to (Whole screen / Trigger layer)
and Inside / Outside. The player, the command (and so MCP and the
control channel) carry it; the command's params doc lists it. Figma has
no such setting, so .fig export writes it as a manual offset. Old files
and JSON without anchor read as screen and write unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 09:19:33 +02:00
bflatastic b00d12b623 #915: get_node refuses a layer too large for maxBytes on its own; a window only an offset made says it is paging 2026-10-24 13:01:33 +07:00
bflatastic b6f5c29d19 #915: MCP get_node pages past a child too large for the limit and keeps the truncation marker 2026-10-24 12:58:10 +07:00
bflatastic 91af3782e0 #915: the inspect response-limit tests join the MCP crate's one test binary (tests/it, as on main since #1208) 2026-10-24 12:57:58 +07:00
bflatastic 13eba6a3ea Merge main into #1003
# Conflicts:
#	crates/engine/src/lib.rs
2026-10-24 12:53:57 +07:00
bflatastic 1b41d129e6 Merge main into #915 2026-10-24 12:53:52 +07:00