Commit Graph
391 Commits
Author SHA1 Message Date
BFlat b64f13ac56 Merge pull request #1759 from dotCooCoo/feat/export-color-profile
Export: each export setting chooses a color profile
2026-11-07 12:54:29 +08:00
BFlat 8d1b183e7d Merge pull request #1913 from tommy1616/feat/traditional-chinese-taiwan
M10: Traditional Chinese (Taiwan) interface draft
2026-11-07 12:52:13 +08:00
BFlat d578f44742 Merge pull request #1878 from TrevorBrown-dev/feat/region-fills
M8: a fill per region: the Fill section and the fill commands edit the regions selected in vector edit mode (#1875)
2026-11-07 12:29:43 +08:00
bflatastic a6f6c42886 Merge remote-tracking branch 'origin/main' into landb/1913 2026-11-07 11:28:35 +07:00
bflatastic 02f5a3520a Merge main into #1759 2026-11-07 11:19:38 +07:00
bflatastic f4f16f1ba1 Merge main into #1913 2026-11-07 10:57:11 +07:00
bflatastic a864c9beac Merge main into #1997 2026-11-07 10:50:56 +07:00
bflatastic 01096b90f1 Merge main into #1997 2026-11-07 10:50:30 +07:00
Trevor BrownandClaude Fable 5.1 13937c70eb Merge upstream/main into feat/region-fills
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-06 22:39:44 -05:00
bflatastic 05530b794e Merge main into #2049 2026-11-07 09:42:38 +07:00
Trevor BrownandClaude Fable 5.1 e4b3815439 Merge upstream/main into feat/region-fills
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-06 21:40:15 -05:00
bflatastic 3ebd14ad2d Merge main into #1876 2026-11-07 09:10:47 +07:00
bflatastic a632d63602 Merge main into #2049 2026-11-07 09:10:44 +07:00
bflatastic 88f6986bda Merge main into #2049 2026-11-07 08:58:17 +07:00
bflatastic e65c46761a Merge main into #1913 2026-11-07 08:55:12 +07:00
bflatastic 79d6335c90 Merge main into #2047 2026-11-07 08:54:47 +07:00
Trevor Brown 8f1a3a35ff Merge remote-tracking branch 'upstream/main' into feat/region-fills 2026-11-06 20:52:20 -05: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
bflatastic a170ca7e3e Merge main into #1913 2026-11-07 08:45:02 +07:00
bflatastic 7e9a853da6 Merge main into #2047 2026-11-07 08:43:16 +07:00
bflatastic 593a689280 Merge main into #2049 2026-11-07 08:39:43 +07:00
Filip StrajnarandClaude Opus 5.5 7f62fc1416 M5: proto.settings refuses an unknown device and answers the settings (#2027)
`proto.settings` took any string as the device ("Pixel Nonexistent", "iphone 16") and saved it in
the file: the presenter then drew no device frame while its picker showed the name as if it were
one. An unknown colour was already refused with the device's colours, but a typo in the device
went unnoticed, and an agent couldn't read the settings back (`proto.settings {}` answered
`{ok: true}`, and neither `inspect_document` nor `get_node` shows a page's prototype settings).

An unknown name, or a value that isn't a string, is now refused with every name the picker offers
("None", "Custom size" and the devices), and nothing changes. A known name is taken in any case and
with spaces around it, and stored as the picker spells it (`known_device`). Every call answers the
page's settings as they are after it (`settings_json`): device, background, rotation, the colour
shown, the screen as presented (null without a device), the custom size and responsive, beside the
`ok: true` it answered before; `proto.settings {}` reads them.

Test: `proto_present_tests::unknown_devices_are_refused_and_settings_read_back`. Docs: mcp.md,
prototyping-parity.md, ROADMAP.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvG6E2LxvJ6MuFmiLA6Zqm
2026-11-06 12:44:01 +01:00
Filip StrajnarandClaude Opus 5.5 853e8c6cc4 M8: a minimised window under Wayland answers control requests (#1906)
Under Wayland an app can't tell that its window is minimised (no `minimized`, no `occluded`),
and the compositor stops asking it to draw. eframe keeps running the app's logic for a window
whose redraw is overdue, but `control::background` still left every request for "the window's
frame", which never came: with the window minimised on GNOME or KDE Plasma each control-channel
or attached-MCP request waited and timed out after 60 s ("it was not applied").

`control::background` now treats a window that drew no frame for `NOT_DRAWING_AFTER` (0.2 s)
while a request waited for it as hidden: requests that act on the app's state are answered from
the logic passes, as they are for a window the platform reports minimised. `FigApp::control_waits`
keeps, per window, its pass count and the time the wait began; a frame drawn since starts the
wait again, and a pass is asked for 0.2 s later while a request waits. Screenshots and `ui.input`
still wait for a drawn frame. Measured on GNOME Wayland with the window minimised by its own
button: 0.54 s a request (before: 13.7 s for one, then 60 s timeouts).

Test: `idle_tests::a_window_that_stopped_drawing_answers_from_its_logic_passes`. Docs:
control-protocol.md, mcp.md, hardware-parity.md, target-app-parity.md, ROADMAP.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvG6E2LxvJ6MuFmiLA6Zqm
2026-11-06 12:27:59 +01:00
Filip StrajnarandClaude Opus 5.5 fb55872450 M9: one auto layout edit on each side of a branch is one conflict (#2026)
A padding changed on both sides of a branch gave a conflict for every size and place auto
layout worked out from it: the component's, each instance's and their hugging parents' width
and height, each instance's layout, and every child's position (10 in a file with one instance,
138 with 15). The merge was refused until each had a side, yet the merged file laid itself out
again from the padding whatever they got. A `layout` conflict also took all auto layout fields
together, so a gap changed only on the branch was lost when main won the padding.

`merge::plan` now leaves out properties both sides changed to values both files work out for
themselves (`Plan::derived`): a hugging auto layout frame's or a filling child's size, text
sizing to its characters, an auto layout child's place (when both sides turn and scale it the
same way), and a value a layer in an instance has as its main's layer has it. Merging keeps the
file merged into's own value and normalising lays it out from the merged inputs.

`layout` and `childLayout` merge field by field: a field changed on one side (or the same way on
both) merges, and each field changed both ways is a conflict of its own (`Key::Field`,
`<id>/layout.padding`), written whole from the merged fields and each picked side's value. The
review shows it as "auto layout padding" with both sides' values ("8, 8, 8, 8").

Tests: `branch_tests::one_conflicting_auto_layout_edit_is_one_conflict` (the issue's file: one
conflict, either side laid out as if set directly), `auto_layout_fields_changed_one_way_survive_a_conflict`
(merge and update from main), `sizes_auto_layout_works_out_never_conflict`; the merge fuzz test
leaves worked-out values out of what each side must show. Docs: collaboration-parity.md, mcp.md,
ROADMAP.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvG6E2LxvJ6MuFmiLA6Zqm
2026-11-06 12:17:11 +01: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
fluid-cheyandClaude Opus 5.5 fccaa43101 M9: a joined live session keeps reconnecting and says when it's disconnected
A session you joined could end for reasons that pass, or after about 85 s of
retries, and its tab then looked like an unsaved local file. Work done there
reached no one, with only a 3 s toast to say so.

A live connection now reconnects until you leave, pausing a minute after each
12 attempts. Only refusals that are for good stop it, so "too many messages",
"busy" and the like retry. Messages queued while offline go out paced so a long
queue can't trip the server's rate limit. A session that ended stays on its tab:
the tab shows a red mark and a strip across the canvas offers Retry now or Try
again, Save a copy and Dismiss. Joining again uses the same tab, and what changed
offline opens beside it as an unsaved copy. A session that gets out of step
joins again by itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:17:25 -07:00
SHXJ 9770f1696e M10: Traditional Chinese (Taiwan) interface draft
Add a 2,647-row zh-Hant catalog (1,975 UI strings, 641 command labels, 31 menu wordings), locale mapping for zh-TW/zh-HK/zh-MO/zh-Hant, and a Noto Sans CJK TC-first fallback. The catalog is marked draft pending native review; the web picker stays hidden because craft-fonts currently ships only the Simplified CJK face.
2026-11-03 22:56:03 +08:00
Trevor BrownandClaude Opus 5.5 d2e7c49e57 Region fills: each region once, no override for a no-op, Copy fill reads the region, layer-only controls off region rows
Named regions are deduplicated; an inheriting region stays inheriting when an edit leaves its stack as it was, and its own stack is only replaced once the edit succeeds; fill.copy reads the selected regions' paint; the Fill section leaves the fill style, a variable bound to the layer's fill and the image crop off region rows. Tests in regions_tests and region_fill_rows_tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 09:14:55 -05:00
Trevor BrownandClaude Fable 5.1 2e392e99b1 M8: a fill per region: the Fill section and the fill commands edit the regions selected in vector edit mode (#1875)
fill.set/add/remove/move/paste on the edited layer go to the selected regions' own fill stacks (or those named by regions), the layer's fills untouched; the Fill section and the colour picker list them. Test regions_tests::the_fill_commands_edit_the_selected_regions; docs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-02 07:55:27 -05:00
Trevor BrownandClaude Fable 5.1 695ca07528 M3: text styles carry paragraph spacing and indent, the list style and the OpenType features (#1829)
ParagraphStyle beside TextStyle in StyleKind::Text: style.create takes it from the layer or a value, style.apply sets it (the list on the paragraphs touched for a range), style.update moves it, editing it unlinks, .fig files keep it with the style; tests in styles_behaviour_tests and fig_export_assets_tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-02 06:43:02 -05:00
Trevor BrownandClaude Fable 5.1 5050631bcf M5: Hide UI in the presenter: the bars go away and Cmd/Ctrl+\ brings them back (#1820) (#1826)
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 23:50:03 -05: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
BFlat 12e9b409ed M12: a connector end on any part of an object with Cmd/Ctrl (#1733) 2026-10-31 10:27:56 +07:00
BFlat 6b28ef57dc M4: Dash cap setting for Dash and Custom strokes (#1728) 2026-10-31 09:41:39 +07:00
BFlat 87254913af M3: library analytics list how each style and variable is used (#1730)
* M3: library analytics list how each style and variable is used

A file reports how many of its layers use each of a team library's styles and
variables, and Show usage lists them by a Type choice with their layers and
files. Layers in instances count for their component. Also ignores plugins/target.

* M3: the style analytics test waits for the instance report too
2026-10-31 09:13:08 +07:00
BFlat 2e92d36af0 M6: SwiftUI and Compose code name a bound variable by its code syntax (#1716)
Bound variables with an iOS or Android code syntax are named in generated SwiftUI and Compose in place of their value: paints, stroke weight, sizes, corner radius, gaps, padding, opacity and text. A variable with none for the platform keeps the value.
2026-10-31 06:49:41 +07:00
BFlat 920aafb805 M1: the colour picker's Image tab has a placeholder, a preview and takes a dropped file (#1682) (#1713)
Choosing Image shows a checkered placeholder and Upload from computer instead of a dialog at once; an image paint shows its asset preview. A file dropped on the open picker replaces the image, keeping the fill mode, crop, position and adjustments (image.replace, one undo step; paths capped as image.place's). The browser's file picker reaches the tab too. Also fixes a race in library_detach_tests.
2026-10-31 06:32:59 +07:00
BFlat b462a48346 M12: a connector's path can be reshaped by dragging its path handles (#1710) (#1717) 2026-10-31 04:53:36 +07:00
BFlat 4bcfd5247f M12: boards have 16 more shapes, in grid pickers (#1705)
* M12: boards have 16 more shapes, in grid pickers

* M12: a board shape's id and name sit in one table
2026-10-31 04:27:12 +07:00
BFlat 2d784c363d M3: library analytics count insertions and show them over time (#1704)
Gap 10: a file counts the library instances it places, creates, pastes or duplicates and reports them like its detaches. The server counts what a file's totals grow past their highest in the current week and keeps a year of weeks (actions.json). library.usage {days} and Show usage give insertions and detachments over the past 30 days, 90 days or year. New variants are appended; an older server refuses them and Show usage says so.
2026-10-31 04:13:14 +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
BFlat fd8d49fc95 M11: a widget opens its plugin's panel (plugin API 5) (#1684)
* M11: a widget opens its plugin's panel (plugin API 5)

Gap 6: a widget callback's ui_show opens the plugin's panel and keeps the module loaded; the panel's callbacks get the widget's id and state and redraw it in the same undo step. Older plugins' ui_show from a widget still opens nothing. The Counter example sets its count from a panel.

* M11: open_panel takes its opener as one value
2026-10-31 03:24:32 +07:00
BFlat 760aece238 M5: an Open link action can open in place of the prototype, with Open in new tab off (#1685) 2026-10-31 02:37:54 +07:00
BFlat bbd1188c81 M3: a collection of this file can be extended (#1679)
Gap 14: variable.extendCollection and Extend collection on a collection's row in the Variables editor. Extensions follow edits of the collection they extend at once (normalising keeps them in step whenever variables change), extensions extend again, and a loop of extensions from a hostile file comes apart into plain collections.
2026-10-31 02:21:53 +07:00
BFlat f152ca995f Merge pull request #1697 from saimskywalker/issue-1095-roots-refuse-network
M8: commands confined to automation roots reach no server (#1095)
2026-10-31 02:10:56 +07:00
saimskywalker e6059b3543 M8: confined library checks say they were refused (#1095)
library.check refused nothing while automation roots confined it: it
counted libraries it never asked about and remembered them as yours. It
now refuses before remembering any. library.updates and library.list
answered a refused check with the team library cached earlier, as if the
server had nothing newer; the refusal now reaches them instead of
falling back like an offline server. The MCP and control protocol docs
say that a root on a network share still reaches that file server.
2026-10-31 02:43:50 +08:00
BFlat 9f4d1ce75b M12: board votes say who voted for what, and earlier votes' results are kept (#1666)
* M12: an ended vote's results say who voted for each object

* M12: a board keeps earlier votes' results, listed in Results and deleted one at a time

* M12: the vote Results popover is drawn by small helpers, and its stepper, rows and Clear are tested
2026-10-31 01:19:03 +07:00
saimskywalker ef4c0aacfd M8: commands confined to automation roots reach no server (#1095)
The roots confined files only: a confined agent could still publish the
open document to any server, host it on the network or join a session.
The engine reaches the network only through the live link, which now
refuses a call made while the roots confine it (hosting, joining,
publishing, sharing, team libraries, plugin directories), and skips the
background team library checks. The MCP and control protocol docs say so.
2026-10-31 02:05:29 +08:00
BFlat be6a7af25f M3: team library analytics count detached instances (#1668)
* M3: team library analytics count detached instances

Detaching an instance of a library component counts it in the file (libraries.detached, saved with it, capped). The background library check reports the counts after its fetch (ReportDetached); the server keeps them per file beside the file's usage report in usage.json (same caps) and answers Detached with them. Show usage and library.usage list detaches per component and per file (detached, detachesKnown). The new request and reply variants are appended, so every older library message keeps its bytes; an older server refuses the new requests, which the app ignores. Gap 10.

* M3: the detach test waits for the instance count too (the two arrive in separate reports)
2026-10-30 23:29:18 +07:00
Claude 876d458414 Merge main into claude/sharp-feynman-me8n1o
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Tayw8nmxoLLmKv13jK93z
2026-10-30 10:45:10 +00:00