Commit Graph
517 Commits
Author SHA1 Message Date
bflatastic af0c1b2ba7 Merge main into #2050 2026-11-07 10:01:19 +07:00
bflatastic 05530b794e Merge main into #2049 2026-11-07 09:42:38 +07:00
bflatastic 905144708b Merge main into #2050 2026-11-07 09:35:50 +07:00
bflatastic e47c91e131 Merge main into #2048 2026-11-07 09:21:25 +07:00
bflatastic 748a75cdd3 Merge main into #2050 2026-11-07 09:20:52 +07:00
BFlat 5b39d5deaa Merge pull request #2018 from TrevorBrown-dev/fix/ui-script-fallbacks-2014
M10: the UI takes a system face for Ethiopic, the Indic scripts and the other scripts its fonts lack (#2014)
2026-11-07 10:14:14 +08:00
bflatastic 7af6a46f58 Merge main into #2048 2026-11-07 09:11:03 +07:00
bflatastic a632d63602 Merge main into #2049 2026-11-07 09:10:44 +07:00
bflatastic cd3c3a2fd4 Merge main into #2050 2026-11-07 09:10:30 +07:00
BFlat 8ea82be66b Merge pull request #2047 from StrajnarFilip/claude/fix-1906-minimised-control
M8: a minimised window under Wayland answers control requests (#1906)
2026-11-07 10:09:09 +08:00
bflatastic 1c57967484 Merge main into #2018 2026-11-07 09:00:16 +07:00
bflatastic 562d83c161 Merge main into #2048 2026-11-07 09:00:13 +07:00
bflatastic c7570645e8 Merge main into #2050 2026-11-07 08:59:37 +07:00
BFlat 47b5979ef0 Merge pull request #2012 from TrevorBrown-dev/feat/text-direction-1830
M4: a paragraph's base direction can be forced: Text ▸ Text direction ▸ Left to right / Right to left (#1830)
2026-11-07 09:58:59 +08:00
bflatastic 88f6986bda Merge main into #2049 2026-11-07 08:58:17 +07:00
bflatastic bb26014a7a Merge main into #2048 2026-11-07 08:57:59 +07:00
bflatastic 32f2f119f9 Merge main into #2050 2026-11-07 08:57:25 +07:00
bflatastic 3b1fc77ba1 Merge main into #2018 2026-11-07 08:57:17 +07:00
bflatastic 79d6335c90 Merge main into #2047 2026-11-07 08:54:47 +07:00
Trevor BrownandClaude Fable 5.1 aae6f55592 Merge upstream/main into fix/ui-script-fallbacks-2014
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-06 20:44:15 -05:00
bflatastic 7e9a853da6 Merge main into #2047 2026-11-07 08:43:16 +07:00
bflatastic 22abeefae2 Merge main into #2048 2026-11-07 08:41:38 +07:00
bflatastic 8523dd43b4 Merge main into #2050 2026-11-07 08:41:04 +07:00
bflatastic 468cab6b2b Merge main into #2055 2026-11-07 08:40:31 +07:00
Trevor BrownandClaude Fable 5.1 5a63c08a78 Merge upstream/main into feat/text-direction-1830
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-06 20:40:21 -05:00
Trevor BrownandClaude Fable 5.1 f66b51c7f7 Merge upstream/main into fix/ui-script-fallbacks-2014
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-06 20:40:19 -05:00
bflatastic 593a689280 Merge main into #2049 2026-11-07 08:39:43 +07:00
bflatastic 9a6cc3e546 Merge main into #2047 2026-11-07 08:30:28 +07:00
bflatastic 162aeb4bda Merge main into #2048 2026-11-07 08:29:53 +07:00
bflatastic 704720d0e1 Merge main into #2050 2026-11-07 08:29:20 +07:00
bflatastic 75847329df Merge main into #2049 2026-11-07 08:29:16 +07:00
bflatastic 642d847329 Merge main into #2055 2026-11-07 08:28:47 +07:00
bflatastic 7214854ed5 Merge main into #2046 2026-11-07 08:26:08 +07:00
bflatastic 30f149b939 Merge main into #2049 2026-11-07 08:11:36 +07:00
bflatastic 9ffe6b07e4 Merge main into #2046 2026-11-07 08:02:46 +07:00
bflatastic 7e316c2cf1 Merge main into #2047 2026-11-07 08:01:58 +07:00
bflatastic 9cbe2098c0 Merge main into #2048 2026-11-07 08:00:52 +07:00
bflatastic 3f3f42630c Merge main into #2050 2026-11-07 07:59:58 +07:00
bflatastic b73c51c978 Merge main into #2024 2026-11-07 07:58:09 +07:00
bflatastic ae4d0c912e Merge main into #2055 2026-11-07 07:55:49 +07:00
Oleksii Sukholita 43403b3fed M8: glass takes one sample where its colours split under 1/256 px
The dispersion window samples the backdrop 18 times a pixel wherever the
curve bends it, even where the move is so short that the channels split
by a sliver of a pixel. Under 1/256 px every step lies within that of
the moved point, so one sample is within one level (of 255) of the window
over any backdrop, the hardest being 1 px columns of black and white or
of white and nothing. Those pixels now take one sample.

A larger split doesn't qualify: at 1/4 px one sample would move red and
blue by 1/8 px, up to 32 levels across a hard edge.
2026-11-06 19:56:36 +01:00
Oleksii Sukholita 104aa3deb1 M3: a glass's bound light angle turns round past ±180 instead of stopping
`Glass::set_bound` clamped a bound light angle to ±180, so a variable of
270 drew the light from 180, a quarter turn off, while a typed 270 draws
from -90. A bound angle now turns round into -180–180 (270 is -90, -0 is
0), the same direction the angle draws in.
2026-11-06 19:39:12 +01:00
Oleksii Sukholita 5bdedc3930 M3: a layer binds an effect's field only when its effect has it
`variable.bind` on a layer checked only the variable's type against the
field's scope, so `effects/0/refraction` on a drop shadow, or a binding on
an effect the layer doesn't have (`effects/9/radius`), was kept and did
nothing. An effect style already refuses both. The layer now uses the same
check: effect `i` must exist and have the field, and the field's type
comes from that effect. A binding an older file has on such a field still
does nothing, and still unbinds.
2026-11-06 19:38:52 +01:00
Filip StrajnarandClaude Opus 5.5 20f1219fbc M1: Set default properties gives new shapes and text the layer's properties (#2028)
Edit ▸ Set default properties put the selected layer's properties into the Paste properties
clipboard and did nothing else: a rectangle drawn afterwards was still the default grey, and what
Copy properties had copied was replaced.

The defaults are now kept apart (`Session::default_props`: one set for shapes, set from any layer
but text, and one for text). Rectangles, ellipses, polygons and stars drawn or clicked with their
tools take the shape defaults: fills, strokes and their settings, effects, opacity, blend and
corners (only where the shape has corners), through `apply_copied_props`, the rules Paste
properties already used (now shared). New text from the Text tool takes a text layer's fills and
font and is laid out at that size. Frames, sections, slices, lines, arrows, vectors and layers made
by commands keep their own look; the help center is silent on those. `edit.setDefaultProperties`
answers `{for: shapes|text, properties}`.

Tests: `default_props_tests::new_shapes_drawn_with_the_tools_take_the_default_properties`,
`setting_defaults_leaves_the_copied_properties_alone`,
`new_text_takes_a_text_layers_defaults_and_shapes_keep_theirs`. Docs: ui-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:55:21 +01: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 98710acb80 M7: closing a tab with unsaved changes asks Don't Save / Cancel / Save (#2032)
Closing a tab whose file has unsaved changes, by its X, a middle click, File ▸ Close tab or its
shortcut, only showed a toast ("save first, or close again with Alt to discard"; from the menu
"close the tab with Alt-click") and left the tab open, so closing a tab seemed not to work. A
`file.close` refused for unsaved changes asked the UI with `dialog.unsaved`, which toasted too.

Closing such a tab now asks the unsaved-changes question the window asks when it closes
(`windows::unsaved_question`, shared): Don't Save closes the tab and drops the changes, Cancel or
Escape keeps it, Save saves the file next frame and then closes the tab. A file without a path, or
saved as .fig, asks where with Save as first; the tab closes once that saved it and stays open if
it is cancelled or fails. `UiState::close_tab` (`windows::TabClose`: Ask, Save, AfterSaveAs) keeps
where it is; `ask_close_tab` starts it for the tab strip, the menu and `dialog.unsaved`. Alt+close
still drops the changes without asking. The three toasts are gone from the code and the catalogs.

Reordering tabs by dragging, the issue's other half, already works on main (landed 2026-10-19,
`dragging_a_tab_along_the_bar_reorders_it_without_detaching`).

Tests: `titlebar_tests::closing_a_tab_with_unsaved_changes_asks` (the X asks, Escape keeps the tab,
Don't Save closes it, Alt+click closes without asking, File ▸ Close tab asks),
`titlebar_tests::save_in_the_tab_question_saves_then_closes` (Enter saves then closes; no path:
the tab stays). The title bar test harness can hold modifier keys. Docs: ui-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:39:39 +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
Trevor BrownandClaude Fable 5.1 2d4e1ce4e8 Merge upstream/main into feat/boolean-button-icons-2003
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 17:38:32 -05:00
Trevor BrownandClaude Fable 5.1 ee033de32e Merge upstream/main into fix/ui-script-fallbacks-2014
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 17:38:30 -05:00