Commit Graph
137 Commits
Author SHA1 Message Date
bflatastic d601cc873b Export: rustfmt the merged lines 2026-11-07 11:36:06 +07:00
bflatastic 02f5a3520a Merge main into #1759 2026-11-07 11:19:38 +07:00
BFlat 11b2ddfaf4 Merge pull request #1958 from MortenHusted/fix/import-variable-id-collisions
M3: importing a file keeps its own variables and styles when their ids are already taken
2026-11-07 10:03:52 +08:00
BFlat 5762212ced Merge pull request #2006 from JonRC/fix/jpeg-quality-srgb-icc
M6: JPG exports take a quality and embed the sRGB profile (#2004)
2026-11-07 09:47:15 +08:00
Denis GomesandClaude Opus 5.5 76af4e3f43 M3: Go to main component opens the library file of a library component
On an instance of a library component the command refused with "open
that file to edit it". The help center describes it opening the file that
holds the main ("Edit main components"), so it now does: the linked
library file opens in a tab, or its open tab comes to the front, on the
main's page with the main selected and zoomed to.

The library keeps the file's layer ids, so the copy's id map
(ImportedComponent::ids) leads from the local copy back to the exact
layer: a nested instance reaches its own main and a variant that variant.

The new tab takes the canvas size of the tab it came from. A tab still at
the default 1200x800 is refitted to the whole page on its first frame,
which would replace the zoom to the main.

A main deleted from the library file since it was published leaves the
file open with a note. A moved library file, a team library on a server
and a path off this computer are refused before anything opens; the last
uses the same check that guards library reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 02:21:20 -05:00
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
MortenHustedandClaude Opus 5.5 024b7fe452 M3: importing a file keeps its own variables and styles when their ids are already taken
`file.import` of a FigCraft file kept the document's style, variable or
collection for every id both files used, and the pasted layers kept
those ids. Ids are numbered per file, so two files made apart share
them, and the imported layers were bound to the document's unrelated
variables, modes and styles.

`append_pages` now gives the incoming file new ids first (`Renames`):
- an incoming style, variable or collection is shared, as before, only
  when the document has the same under its id and everything it names
  is shared too (the same file imported again); a collection and its
  variables are shared only together; library copies keep their ids;
- every other id the document already uses gets a new one from its
  allocators, which now take ids to avoid (the incoming file's own);
- layers' style links, bindings, explicit modes and prototype actions,
  styles' bindings, variables' collections, aliases, calculations and
  extensions, and the renamed collections' mode ids follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:14:20 +01:00
dotCooCoo b8fa8b87be Export: a converted JPG flattens translucent pixels before the conversion
A JPG exported in a profile other than the document's was converted while
still translucent and then composited over white, so translucent fills,
shadows and antialiased edges came out a few levels off what the document
shows. The render is now flattened over white in the document's profile
first. PNG and WebP keep their alpha and are unchanged.
2026-10-31 03:20:20 -07:00
dotCooCoo afc5e13972 Export: each export setting chooses a color profile
Each export setting has a color profile: Same as file (the default, the
document's profile when it exports), sRGB or Display P3. PNG, JPG and
WebP settings list it in their advanced settings, and export.add,
export.update, file.export, file.exportLayers (for every setting) and
the MCP export tool take it as colorProfile. The rendered pixels are
converted to the chosen profile (out-of-gamut colors clip) and the file
is tagged as before: an sRGB chunk for sRGB PNG, the generated ICC
profile (and cICP in PNG) for Display P3; sRGB JPG and WebP stay
untagged. A setting keeps its choice when its format changes, and
settings saved without one read as Same as file.

SVG, PDF and Android XML show no choice and stay sRGB; PDF has no
output intent yet.
2026-10-31 03:07:17 -07:00
Christopher Linnett bc76665c65 M3: colour variables can be calculated from others, faded or mixed per mode (#1280)
A colour variable's value in a mode can now be {"alpha": {"variable", "opacity"}} - that
variable's colour with its alpha replaced - or {"mix": {"from", "to", "t"}}, a per-channel blend
of two colour variables or literals over the stored values (in the document's colour profile),
alpha included. Calculations resolve lazily like aliases, so bound layers, mode switches,
instances and undo follow their sources; they compose, and a loop, or a calculation that reads
more than 32 variables in its collection's modes (other collections in their default modes), is
refused when it is set. Operands are stored by id and read by exact id, as stored references
are. Files keep them; REST, CSS, Penpot and .fig exports write the colour they show in each mode;
deleting a source leaves its readers on the colour they showed; library import and swap
re-point their operands. The Variables panel shows them with their formula and can swap one for
an alias or detach it to its colour.
2026-10-30 16:25:26 +08:00
BFlat c47177498d M8: every file a command reads by path is a regular file within a cap (#1114) (#1625) 2026-10-30 13:48:26 +07:00
BFlat b2885452fb M8: doc.list gives AI agents and plugins file names, not paths (#1117) (#1620) 2026-10-30 12:14:34 +07:00
BFlat 974ab39503 M8: keep unsaved files when a panic stops the browser build (#1123) (#1580)
wasm32 can't catch a panic, so the command and frame guards do nothing in the browser and a panic ended the page with its unsaved changes. The page now follows its files with unsaved changes after every frame; its panic hook writes them to local storage as .figcraft copies (as many as fit) and says on the page that it stopped. The next page opens them again as unsaved files: file.recover also takes the copy as base64 data. The recovery-copy helpers move from the desktop app into figcraft_engine::branches so both share them.
2026-10-30 06:36:33 +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
bflatastic 0a6642747a Merge main into #1143
file.open and file.export docs: main's (reload, export options) plus the Penpot format.
2026-10-29 13:51:11 +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 1e22268602 Merge main into #1143 2026-10-29 12:44:42 +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 f154011299 Merge main into #1461 2026-10-29 11:19:05 +07:00
bflatastic 9202b1032f Merge main into #1460 2026-10-29 10:38:06 +07:00
bflatastic d3551f3c6c #1432: accept components and instances as thumbnails; move the tests to thumbnail_tests.rs 2026-10-29 10:29:06 +07:00
Dylan M. Taylor 23f57c8660 refactor(engine): simplify command handling for strict clippy 2026-10-28 16:00:36 +00:00
MortenHustedandClaude Opus 5.5 8fadd5d8e5 #1460: file.exportSvg's doc says which of file.export's options it takes
"(as file.export)" read as all of them, but the PDF and XML options are
refused there, so it now says they are file.export's SVG options.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:46:27 +01: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 64e648ae7e M0.9: command docs list every parameter their commands read
Before commands can refuse names their docs don't list (#1413), the docs
have to list what the code reads. These are the top-level names the run
code (and every helper it hands the params to) reads that its doc left
out, all checked against the code; nothing changes behaviour:

- creation: node.create (characters, points, closed), frame.create,
  shape.*, text.create and vector.create (index, name, select),
  image.place (name)
- aliases the code already took: comment.* (id | link), annotation.add
  and annotation.edit (label | text), text.set (characters | text),
  text.listStyle (style | list), fill.add and stroke.add (paint | color),
  proto.interaction.add/set (durationMs | duration_ms, delayMs | delay_ms)
- options: file.export and export.update (contentsOnly,
  includeBoundingBox, svgOutlineText, svgSimplifyStroke), comment.next
  (frame, resolved), layout.suggest (direction), variable.create
  (description), vector.edit.widthPoint (side), collab.share (key),
  collab.manageFile (access, label, link, name), fill.pattern (visible,
  blend)
- docs whose shape hid a name: proto.interaction.add (a stray `}` closed
  the object before delayMs; dest was only in a trailer),
  proto.interaction.set (patch fields in a second object), collab.join
  (a top-level comma inside the link examples), pref.uiScale (commas in
  the value's description)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:06:33 +01:00
Guillermo Bueno 7ea161f33f M6: merge main and isolate offline Penpot import/export 2026-10-27 18:59:49 +01:00
aiongg a9d93f8310 refactor(engine): split cmd/file.rs into file/ 2026-10-26 20:17:48 +00:00
aiongg 96423f5a6e refactor(engine): move cmd/file.rs zip_cap_tests to file/zip_cap_tests.rs 2026-10-26 20:17:48 +00:00
aiongg 13855cfabe refactor(engine): move cmd/file.rs staging_write_tests to file/staging_write_tests.rs 2026-10-26 20:17:48 +00:00
aiongg 47c807f209 refactor(engine): move cmd/file.rs rest_import_tests to file/rest_import_tests.rs 2026-10-26 20:17:48 +00:00
aiongg f0f1c2003d refactor(engine): move cmd/file.rs tab_order_tests to file/tab_order_tests.rs 2026-10-26 20:17:48 +00:00
bflatastic 2aae395310 Merge main into #840 2026-10-27 00:31:32 +07:00
BFlat ad9abe7ce6 Merge pull request #1133 from g1obalworlds/fix-batch-rollback-closed-tabs
engine: restore tabs closed inside a failed command group
2026-10-27 00:13:37 +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 b90749b377 Merge main into #1133 2026-10-26 22:57:23 +07:00
Ridzzz0Alam 0d7d9ebd00 M1: stamp the file before reading it, so a change made during the read isn't missed (#1330)
Open and Revert to saved recorded the file's size and modification time after reading it: a change written in between would be stamped as already loaded and a later save would replace it. Taking the stamp first makes such a change refuse the next save instead (CodeRabbit on #1374).
2026-10-26 11:46:23 +01:00
Ridzzz0Alam a648700fcc M1: Save no longer overwrites changes another app made to the open file (#1330)
With a file open in the desktop app, figcraft-cli could change it on disk;
the next Save (or auto-save) wrote the app's copy over it and said
"Saved", losing the CLI's changes.

Each tab now remembers its file's size and modification time when it
opens, reverts or saves it (the Stamp library files already use), and
Save and auto-save refuse while the file on disk differs, pointing to
Revert to saved or Save as. Scripts pass `file.save {overwrite: true}`.
Save as and saves to an explicit path are unchanged.
2026-10-26 11:04:01 +01:00
BFlat 8e79ffc3d7 Merge pull request #1021 from sarsonj/feat/sketch-import
M6: .sketch import (Sketch 48 and newer)
2026-10-26 11:10:16 +07:00
BFlat d203f8fdcf Merge pull request #698 from Dubblbindong/firma/fix/plugin-permission-to-save-a-file-86
feat(plugin): add saveFile permission with host-owned save dialog
2026-10-26 10:55:11 +07:00
bflatastic 27d1089847 Merge main into #1021
# Conflicts:
#	ROADMAP.md
#	docs/target-app-parity.md
2026-10-26 10:43:34 +07:00
bflatastic eb16585ec2 Merge main into #698 2026-10-26 09:20:18 +07:00
bflatastic 7a1b9d8414 #1021: a named style's optical size follows the text size only for .sketch imports
Existing text keeps the named instance's optical size, so documents render as before; the
Sketch import stores the size as the text's opsz value instead.
2026-10-26 09:12:40 +07:00
bflatastic 07a654959a Merge main into #1021
# Conflicts:
#	docs/file-format-parity.md
2026-10-26 09:12:39 +07:00
bflatastic df8edc6b8c #1314: refuse saving over the file only when save_to has a path (main's download has none) 2026-10-26 09:12:21 +07:00
bflatastic 1ec74401e6 Merge main into #1314 2026-10-26 09:11:52 +07:00
BFlat df1bdd0e7c Merge pull request #1010 from Ridzzz0Alam/fix/969-web-save-download
Web build: Save, Save as and Export arrive as downloads
2026-10-26 08:32:39 +07:00
Denis GomesandClaude Opus 5.5 c305e23ab4 M7: don't save over a file whose images past the budget weren't loaded
A native file whose images together pass IMAGE_BUDGET (4 GiB, or 20,000
images) opens without the images past it. They are intact in the file, but
saving it again wrote the archive from the images in memory, so every
save, autosave or `convert` onto itself deleted them for good. A 7.6 GiB
`.fig` converted to a 7.3 GiB `.figcraft` reopened with 1,312 of its 2,337
images: one save would have dropped the other 1,025.

Loading now tells images left out for the budget apart from damaged ones
(`Loaded::images_past_budget`). A tab opened or reverted from such a file
remembers the file, and saving over it is refused with a message that says
how many images would be lost and to save under another name; saving a copy
elsewhere works, and the original keeps its images. Auto-save skips the
tab instead of retrying and failing every minute. `figcraft-cli convert`
and `run --in X --out X` refuse to write over their input the same way.
The same file is matched by its resolved path, so `./a.figcraft` and
`a.figcraft` count as one (`file_access::same_file`).

The note for each image left out for the budget now says it stays in the
file, which can't be saved over without losing it. Damaged and missing
images keep the old wording: they can't be kept, and saving drops them as
before. The doc comment on report_lost_images said saving always drops
the images it reports, which is no longer true for budget ones, so it went.

This stops the data loss; it doesn't make such files editable in place.
Loading images on demand would, and is discussed in the issue this links.

Tests: the count and the note for both budgets; saving over the file
refused with the file unchanged, Save As then working, and saving back over
the original refused again; a snapshot encoded in the background refused
too; a tab without images left out saving as before. Checked by hand on the
7.3 GiB file with file.save, file.saveAs, convert and run --out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 18:31:47 -04:00
Guillermo Bueno 1cd9c0d875 M6: add bounded offline Penpot import and export 2026-10-25 14:36:23 +01:00