Commit Graph
139 Commits
Author SHA1 Message Date
bflatastic 97fa35008b Merge #1010: Web build: Save, Save as and Export arrive as downloads (@Ridzzz0Alam) 2026-10-24 09:35:09 +07:00
bflatastic 732eaeba99 Merge #989: Large .fig files keep their layers and their library links (#981) (@Ridzzz0Alam) 2026-10-24 09:29:56 +07:00
bflatastic 58917a0845 Merge #951: fig import: a note says how much of the file it covers (#467) (@Hamza0101) 2026-10-24 09:29:51 +07:00
bflatastic 9fb88b6668 Merge #904: M1: write grid track sizes in .fig export (#465) (@xiaou61)
# Conflicts:
#	docs/file-format-parity.md
2026-10-24 09:24:04 +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
Ridzzz0Alam f0e75e183b M1: swap defaults past the layer cap say so (#981)
Instance swap defaults that name an in-file main skipped by the layer cap
were reported as "isn't in the file": only instance targets fed the
capped-mains list. Defaults now feed it too.
2026-10-21 21:06:59 +02:00
Ridzzz0Alam 09ff20ffd6 M1: several exported layers arrive as one zip in the browser (#969)
Cause: in the browser, exporting several layers started one download
per file. Chrome holds back every download after the first until the
site is allowed automatic downloads, so the other files didn't arrive.

Fix: file.exportLayers takes `zip: true` (without `dir`) and returns the
files together as one base64 zip `data`, written by a new
figcraft_format::zip_files (the workspace's existing zip dependency).
The UI asks for it when the platform saves by downloads and hands over
one `<document name>.zip`.

Test: ui-egui several_layers_download_as_one_zip (one download, a zip
holding both layers). It fails on the previous commit (two downloads).
2026-10-21 20:35:13 +02:00
Ridzzz0Alam e211bf5476 M1: large .fig files keep their layers and their library links (#981)
A design-system .fig file (54 pages) stopped at 244 248 layers, the rest
of its pages skipped, and most of its instances came in as frames "of
components that aren't in the file", with the cap note shown twice.

- The layer budget follows the compressed canvas at 8 bytes a layer, not
  32: design systems are mostly instances, which pack tightly. Files under
  about 1.6 MB keep the 200 000 floor and the 2 million ceiling stays
  (measured on synthetic files: about 5.4 KB and 19 µs a layer).
- The library copies the pages use (the file's hidden internal canvas) are
  mapped before the pages: at the cap the pages' last layers are skipped,
  not the mains their instances link to. The REST stage keeps building
  pages first (its ids are first-wins) and allows for the wrapper frame of
  each library copy, the one layer the mapper doesn't count; that layer
  tripped the REST stage's own cap and repeated the note.
- Instances and swap defaults whose main is in the file but past the cap
  say so instead of "aren't in the file" (en and es catalogs).
- The .fig import gives a note the mapper already gave only once.

Tests in fig/layer_cap_tests.rs: the budget for a #981-sized canvas, library
mains ahead of the pages at the cap, the note for a capped local main, and
a whole 210 000-layer file with one cap note and its library links kept.
All four fail on the previous code.
2026-10-21 18:22:16 +02:00
Hamza f3e5d2abc4 .fig import: a note says how much of the file it covers
A .fig import's notes were deduplicated and left uncounted, so each kind
was named once with no sign of how much of the file it applied to: a
file whose every text layer lost a setting read exactly like one where a
single layer did. That is what made the loss in #467 invisible -- the
reporter saw one line while three kinds of data went, one of them on all
83 text layers.

The mapper now collects its notes through the same `Warnings` the REST
importer already uses, which keeps the first-seen order and the cap and
appends `(×n)` where a note was met more than once. Nothing else
changes: the note text stays the catalog key it was, and the import
report already reads a `(×n)` suffix off before translating the base
(`ui-egui`'s `note_text`), so a counted note is still translated.
`figcraft-cli info` prints these notes, so it now names each kind with
its count.

With the count dropped again, the test reports the two notes plain:

    ["connector layers import as static frame/vector snapshots ...",
     "connector label was skipped: depth limit prevents nested ..."]

Part of #467.
2026-10-21 00:06:57 +05: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
xiaou61 beb87d8696 M1: describe the fallback the export actually takes (#465)
The warning still said fixed and content-sized tracks were written as equal
tracks, which the export no longer does. It now reports only the tracks whose
size the format can't hold, and only for the tracks an exported grid has.

Co-authored-by: xiaou61 <113765024+xiaou61@users.noreply.github.com>
2026-10-20 11:14:02 +08:00
xiaou61 414aa1b63c M1: write grid track sizes in .fig export (#465)
The import has read `gridColumnsSizing` / `gridRowsSizing` since #630, but the
export wrote only equal tracks and warned about it, so a grid with a fixed
column beside two weighted ones came back equal after a save and reload.

Write the sizing map next to the order map, keyed by the same track ids: fixed
px, fractional fill and content-sized tracks keep their sizes. A track the
format has no name for (negative or out-of-range size, or a document track the
frame doesn't list) keeps its id and reads back equal, with the existing
warning.

Co-authored-by: xiaou61 <113765024+xiaou61@users.noreply.github.com>
2026-10-20 11:04:13 +08:00
MortenHustedandClaude Opus 5.5 017517fbb9 #847: an instance's values are kept under the original definition
An assignment and the layers that read it can name different copies of
one definition (the set's own id on one side, a variant's copy on the
other). The values were kept under the id the assignment names and read
under the id the layer names, so such a value never reached its layer.
Both sides now go through prop_root, the definition the copies were
made from (CodeRabbit on #847). In Untitled UI, 143 assignments name a
copy that no layer reads while another copy of it is read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:53:04 +02:00
MortenHustedandClaude Opus 5.5 c530b4067d M3: .fig imports read property values written only as variables
A component property assignment carries its value in `value`, and in
current files also in the variable form `varValue`. Many assignments
leave `value` empty and say it only in `varValue` (booleans as
boolValue, instance swaps as symbolIdValue, text as textDataValue), and
those were ignored: a hidden layer showed and a swapped icon stayed the
default. In Untitled UI that is 1,643 booleans and 185 swaps, so inputs
showed their hint text, help icon and leading icon on top of the
neighbouring layers. Both forms are read now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:53:04 +02:00
MortenHustedandClaude Opus 5.5 4a54aa733f M3: .fig imports keep the properties variant layers are bound to
Each variant of a component set keeps a copy of the set's property
definitions (an id and a parentPropDefId, no name or type), and the
variant's layers and its instances' assignments use the copy's id. Those
copies were skipped, so imported variants lost every boolean, text and
instance swap binding, and instances lost their property values. In
Untitled UI (8,074 copies) an input's "Hint text" no longer followed its
property. A copy now names the definition it was made from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:53:03 +02:00
BFlat ef535e49b0 M1: REST import keeps slot layers and instances' slot content (#792) (#796)
Fixes #792.

Fixes #792.
2026-10-18 15:52:30 +07:00
BFlat 8629a6f586 M3: variable.import reads variables outside meta and never imports nothing silently (#790) (#793)
Fixes #790.

Fixes #790.
2026-10-18 14:59:14 +07:00
skylar taylor-barrick 3f5f673ebc Merge pull request #777 from skylartaylor/document-link-schemes
Open only web links and local libraries from a document
2026-10-17 14:34:58 -06: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 c8b611a802 Merge fix/rest-grid-tracks into batch 15 2026-10-17 23:17:37 +07:00
bflatastic 68b19422ad M1: REST import reads grid track sizes (#465)
gridColumnsSizing and gridRowsSizing are CSS grid-template strings: px
tracks are fixed, fr tracks fill, auto/min-content/max-content hug,
minmax(_, Nfr) fills and repeat(n, ...) expands; other tracks stay equal
(1fr). Tracks past the axis's own count are left out. Files from .fig
keep reading the tracks from their source.
2026-10-17 22:44:09 +07:00
bflatastic 9a69f79861 #552: write and read space evenly as SPACE_EVENLY_CSS
The format's SPACE_EVENLY is its older name for equal gaps without edge
space, so files keep reading it as space between; true space evenly is
SPACE_EVENLY_CSS, which the export now writes.
2026-10-17 22:39:10 +07:00
bflatastic 6afcfc2641 Merge main into #552 2026-10-17 22:36:20 +07:00
bflatastic 64423c58bc M3: .fig and REST imports keep Hug on auto layout and text (#732)
An auto layout frame a .fig records no primary-axis sizing for hugs on
that axis (auto layout sized it by its content before that axis could be
fixed) instead of turning fixed; an instance that records no auto layout
takes its main's; REST frames whose sizing modes are left out hug (the
documented default); text hugs by its resize mode (auto width both axes,
auto height its height) instead of coming in fixed.
2026-10-17 19:27:35 +07:00
bflatastic 6cdfe2ab8e M3: layers inside instances keep their resizing overrides (#731)
A layer inside an instance can now override its resizing (hug, fill or
fixed, with min and max sizes; override group `resizing`) and its size.
They were refused, and imported ones were dropped at the first relayout.
Kept through rebuilds, undo, save, .fig and REST import, .fig export and
detach; reset per group and with Reset all changes. Position, constraints
and absolute or grid placement stay the main's.
2026-10-17 19:27:35 +07:00
bflatastic 546c09b3ba Merge fix/all-f2 into batch 10 2026-10-17 15:26:45 +07:00
bflatastic 5633453f5b Merge fix/524 2026-10-17 14:40:43 +07:00
bflatastic 460985672c Merge fix/656 2026-10-17 14:36:06 +07:00
bflatastic aa90259f1c Merge fix/617 2026-10-17 14:36:06 +07:00
bflatastic 45c68177d0 Merge fix/695 2026-10-17 14:35:14 +07:00
bflatastic fd98f572d2 Merge fix/635 2026-10-17 14:26:04 +07:00
bflatastic 4a572572db #635: no negative zero from the flipped tint 2026-10-17 14:25:56 +07:00
bflatastic f757044d8c fix/all: rustfmt the merged module list 2026-10-17 14:08:05 +07:00
bflatastic d0539b83e9 #631: follow the decoder rename in #639's connector check
#639 lands first and reads networks with decode_styled; once #631 drops the count it calls
network::decode. Needed whenever #631 is rebased onto a main that has #639.
2026-10-17 14:06:43 +07:00
bflatastic 7cb0b68d73 Merge fix/635 2026-10-17 14:05:09 +07:00
bflatastic 3881cce291 Merge fix/632 2026-10-17 14:05:08 +07:00
bflatastic 3f36adfa90 Merge fix/638 2026-10-17 14:05:05 +07:00
bflatastic add7806197 Merge fix/639 2026-10-17 14:05:02 +07:00
bflatastic de28945484 Merge fix/636 2026-10-17 14:04:59 +07:00
bflatastic 1e85d54952 #635: cap the .fig import warnings like the REST ones
Image filter warnings name fields of the file, so a crafted schema could add any number of
distinct warnings (each checked against all earlier ones). The mapper keeps at most as many as
the REST importer does.
2026-10-17 14:04:00 +07:00
bflatastic 48d74df226 #635: one tint sign for REST file JSON and .fig
Both carry the same image filter value, which the public docs describe as green when lower and
magenta when higher; a positive native tint adds green. Both readers flip it now (the .fig path
did, the REST path didn't). The .fig test message no longer names the product.
2026-10-17 14:03:57 +07:00
bflatastic 2ac4ce3a73 #635: rebase onto #628: the filter tests add their paint fields themselves
The test schema extended the image scale-mode field that only #634 adds; it now adds the
filter containers to the paint message itself, so it works with and without #634.
2026-10-17 13:58:41 +07:00
Skyfuller1303 d84aa7f1e1 M1: import image adjustments from .fig files
Image paints that carry a current filter container now import their exposure, contrast, vibrance (as saturation), temperature, tint, highlights and shadows as native image filters. Neutral values leave the paint untouched. The older color-adjust container alone, both containers together, filters that have no native equivalent (brightness, detail, vignette and unknown fields) and malformed or non-finite values are skipped and reported as import notes. A positive tint in .fig data moves toward magenta, so it is flipped once at the .fig boundary; public REST image filters keep their sign and are not reported as approximated. Imported filters use the approximate native renderer, which the import says once.
2026-10-17 13:58:04 +07:00
bflatastic 3fadbf4ba9 #632: read the network with the decoder #631 left 2026-10-17 13:54:25 +07:00
Skyfuller1303 6233b90ba7 M1: import editable text on a path from .fig files
Supported TEXT_PATH layers import as editable native text on their vector path, including mixed text styles, a start inside a later segment and reversed text direction. The importer converts the segment parameter stored in the file to the native distance along the complete path.

The mapping accepts a connected, unbranched path with one recognized start-segment marker and the default placement above the path. Damaged or unsupported path data keeps the text readable off its path and adds an import note. Other vertical placements, multiline text and ambiguous or disconnected networks are not converted. Underline and strikethrough remain a native path-rendering limitation and are reported. Public REST TEXT_PATH layers and the experimental .fig writer are unchanged.
2026-10-17 13:49:58 +07:00
bflatastic c605bd5781 #631: drop the styled-segment count nobody reads 2026-10-17 13:49:46 +07:00
bflatastic 5fba7e5dba #524: one kiwi value per compressed canvas byte (up to 128M), lower value and document.json ceilings on 32-bit targets 2026-10-17 13:48:56 +07:00
bflatastic ec325c648c Merge main into #631
# Conflicts:
#	crates/format/src/fig/network.rs
2026-10-17 13:46:35 +07:00
bflatastic 99dc199bb8 #656: borrow embedded image bytes and copy only the ones a paint uses 2026-10-17 13:46:22 +07:00