Commit Graph
3110 Commits
Author SHA1 Message Date
bflatastic 22abeefae2 Merge main into #2048 2026-11-07 08:41:38 +07:00
BFlat 401db4beb0 Merge pull request #2046 from StrajnarFilip/claude/fix-2026-merge-layout-conflicts
M9: one auto layout edit on each side of a branch is one conflict (#2026)
2026-11-07 09:39:12 +08:00
BFlat f1a8288d82 Merge pull request #2062 from storytold/carry/1943
fix(doc): stop frame_relative on a looping parent chain
2026-11-07 09:39:06 +08:00
BFlat 83054f771d Merge pull request #1971 from TrevorBrown-dev/fix/connector-end-gap
Boards: connectors attached to a side start 4 pt outside the border, as FigJam's do (#1916)
2026-11-07 09:37:24 +08:00
BFlat 8033dea47f Merge pull request #1901 from Hamza0101/credit/hamza-naveed
docs: credit Hamza Naveed in contributor names
2026-11-07 09:36:48 +08:00
BFlat 97f4459671 Merge pull request #2063 from storytold/carry/1895
fix(format): import a .fig blob once however many hashes name it
2026-11-07 09:35:37 +08:00
BFlat a3460e4711 Merge pull request #1777 from Silvertree2010/fix/h4857-node-reorder-silently-turns-a-ne
Reject negative and fractional indexes in node.reorder
2026-11-07 09:34:48 +08:00
BFlat 97b16702d5 Merge pull request #1900 from Hamza0101/fix/text-layer-name-follows-characters
M4, #1747: a text layer's name follows the characters typed into it
2026-11-07 09:34:20 +08:00
BFlat 9664afe673 Merge pull request #1213 from Salah1St/feat/web-fonts
M7: FIGCRAFT_WEB_FONTS embeds more craft-fonts families in the web build
2026-11-07 09:33:06 +08:00
BFlat 798eedf9f3 Merge pull request #2011 from Carbocode/fix/selection-color-icon
M8: unify Selection colors input and align sidebar icons
2026-11-07 09:31:02 +08:00
bflatastic 162aeb4bda Merge main into #2048 2026-11-07 08:29:53 +07:00
BFlat 6864ffff4f Merge pull request #1778 from Silvertree2010/fix/h4878-node-create-vector-create-silent
Reject invalid entries in vector.create points instead of dropping them
2026-11-07 09:27:01 +08:00
bflatastic 7214854ed5 Merge main into #2046 2026-11-07 08:26:08 +07:00
BFlat 8cbac00bea Merge pull request #2024 from TrevorBrown-dev/feat/boolean-button-icons-2003
M1: the boolean operation buttons carry original icons (#2003)
2026-11-07 09:24:44 +08:00
BFlat 57496ae937 Merge pull request #1783 from Silvertree2010/fix/h5009-page-move-silently-turns-a-negat
Reject negative or fractional index in page.move
2026-11-07 09:21:59 +08:00
bflatastic 638ae9bd29 Merge main into #1895 2026-11-07 08:20:40 +07:00
BFlat d6dc554f66 Merge pull request #2056 from ZIDAN44/fix/2043-variable-picker-height
Styles: the style and variable pickers grow back to their full height (#2043)
2026-11-07 09:20:26 +08:00
bflatastic cd2752cd99 Merge main into #1901 2026-11-07 08:19:03 +07:00
bflatastic 4e5f0f8c04 Merge main into #1900 2026-11-07 08:18:31 +07:00
bflatastic 00fa951fe9 Releasing: name the web size gate without its number 2026-11-07 08:18:04 +07:00
BFlat 754b709610 Merge pull request #2060 from Luis-0508/ci/release-contributor-refresh
Release: refresh contributor credits for each release (#2059)
2026-11-07 09:17:55 +08:00
BFlat 8f2f194cf4 Merge pull request #1787 from Silvertree2010/fix/h5159-variable-movemode-silently-turns
Reject negative and fractional index in variable.moveMode
2026-11-07 09:15:57 +08:00
BFlat c78f6b7080 Merge pull request #2031 from Silvertree2010/fix/2029-bug-snap-to-object-stops-working
Keep object snapping when Snap to Pixel Grid is on
2026-11-07 09:12:11 +08:00
BFlat 09e04e7176 Merge pull request #2037 from denisg0mes/go-to-library-main
M3: Go to main component opens the library file of a library component
2026-11-07 09:06:17 +08:00
bflatastic 1d292f8326 Merge main into #1943 2026-11-07 08:03:47 +07:00
BFlat ded38db8ef Merge pull request #2038 from denisg0mes/clipboard-png-decode
M7: #1360 follow-up: decode a clipboard PNG before pasting it
2026-11-07 09:03:29 +08:00
bflatastic 9ffe6b07e4 Merge main into #2046 2026-11-07 08:02:46 +07:00
bflatastic 9cbe2098c0 Merge main into #2048 2026-11-07 08:00:52 +07:00
bflatastic b73c51c978 Merge main into #2024 2026-11-07 07:58:09 +07:00
BFlat 82dff040d4 Merge pull request #2053 from TrevorBrown-dev/fix/minimized-click-shifts-frame-2051
Minimize: a click that shows the right sidebar no longer moves the frame it selects (#2051)
2026-11-07 08:56:26 +08:00
BFlat 049772335b Merge pull request #2057 from aleksei-suholita/perf/glass-dispersion-narrow-window
M8: glass takes one sample where its colours split under 1/256 px
2026-11-07 08:53:12 +08:00
BFlat 43565b5bf6 Merge pull request #2052 from StrajnarFilip/claude/lan-share-read-only
M9: share a file on the local network read-only, with a view-only code
2026-11-07 08:50:25 +08:00
BFlat acc4ae873d Merge pull request #2058 from aleksei-suholita/docs/glass-splay-centre-and-cap
M3: glass's docs say which centre splay bends away from and where a shape gets no core
2026-11-07 08:48:53 +08:00
Luis 4cacc96631 Release: refresh contributor credits for each release (#2059)
About > Contributors still showed an old snapshot, because nothing
regenerated contributors/contributors.json before a release. Opt in to
the shared refresh from craft-common-actions: a read-only credits job
regenerates the snapshot for the release commit, and the shared release
workflow installs it before every source build. The snapshot is not
committed back.
2026-11-07 00:27:48 +01:00
Filip StrajnarandClaude Opus 5.5 10df192b01 M9: a share on every interface links to an address others reach
A session hosted with `bind` 0.0.0.0 (or ::) kept listening on every
interface but put 0.0.0.0 in its edit and view links, URLs, join codes
and shown address, which nobody can connect to. They now name this
machine's local network address of the listener's family (loopback
without one), on the same port.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvG6DxbDYxuPEA2pJirCLQ
2026-11-06 21:27:47 +01:00
Oleksii Sukholita 3d41394bfc M3: glass's docs say which centre splay bends away from and where a shape gets no core
Splay bends away from the centre of the layer's bounds, as the reference
tool's does: on a ring or an L that centre lies outside the shape, so an
inner edge bends towards the hole. Checked side by side on a ring and an
L at Splay 0% and 100%. The module doc still said splay 0 bends along the
edge's normal; it bends towards the core since #1889.

`MAX_SHAPE_PX` now says it is 4M px on the desktop and 1M on the web, and
that a bigger shape gets no core, so a large glass text or vector can bend
differently once zoomed into, in a 2× export, or on the web.

Docs only.
2026-11-06 20:29:14 +01: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
Trevor BrownandClaude Fable 5.1 36b728304b Minimize: a held press keeps its page point when the canvas moves, not only when it narrows (#2051)
While a press that went down on the canvas is held, the view centre now follows the canvas rect's own move as well as its size change, so a still pointer keeps its page point when the left sidebar or rail appears mid-press.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-06 13:43:32 -05:00
Zinadin Zidan 8842019aa4 Styles: the style and variable pickers grow back to their full height (#2043)
egui lays an area out inside the size it had on the previous frame, and a
scroll area shrinks to fit its content, so once a search or a short list
had made the style or variable picker small, it stayed small, also on the
next open. Both now offer their content the screen's height on every
frame. Their lists still stop at their own caps, and placement keeps them
on screen.
2026-11-07 00:27:11 +06:00
Filip StrajnarandClaude Opus 5.5 b779e46d47 M9: a local view-only share's url is the view URL, not the edit URL
The previous fix made a local share's `link` follow the code shown, but its
top-level `url`, the session's WebSocket URL, still carried the edit token:
`collab.join {link: url}` from a view-only share joined with edit access and
the edit reached the host. The link layer now hands over the view way in as a
link and its URL (`SessionInfo::view`, a `WayIn`, in place of `view_link`), and
`collab.peers` gives a local share's `link` and `url` from the way in for the
code shown (`Live::invite`). Online shares are unchanged.

Regression test: `code_tests::a_local_view_share_s_link_and_url_only_let_people_look`
joins through the `link` and then the `url` over loopback; both are welcomed as
viewers whose edits never reach the host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvG6E2VLhC9d5v8VSsf4ng
2026-11-06 19:12:40 +01:00
Trevor BrownandClaude Fable 5.1 5d21ca8791 Minimize: a click that shows the right sidebar no longer moves the frame it selects (#2051)
The view keeps its centre when the canvas is resized, so the right panel appearing (minimized UI, on the press that selects) shifted the pointer's page point by half the width over the zoom and the move handling took it as a drag. While a button that went down on the canvas is held, a resize now keeps the top-left instead.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-06 12:51:52 -05:00
Filip StrajnarandClaude Opus 5.5 a05201e21f M9: a local view-only share's link is the view link, not the edit link
`collab.share {via: "local", access: "view"}` showed the view-only code but
gave the session's own invite link as `link`, and that link edits: whoever
joined through it could change the host's file. A local share's `link`, top
level and in `sessions`, is now the one that goes with the code shown, and it
follows `collab.share {access}`. Online shares keep the owner's own link there,
as before.

Regression test: `code_tests::a_local_view_share_s_link_only_lets_people_look`
joins through that link over loopback and is welcomed as a viewer whose edit
never reaches the host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvG6E2VLhC9d5v8VSsf4ng
2026-11-06 18:45:44 +01:00
Filip Strajnar a2be6632eb M9: share a file on the local network read-only, with a view-only code
The session the app hosts for a local network share now has two secrets:
the edit code and a view-only code (same address and port, a 44-bit secret
of its own). Whoever joins with the view code is welcomed as a viewer: they
see every change and presence, the app makes the file read-only for them,
and the session refuses their batches, as a server does for view links.

Share > Local network offers Can edit / Can view before sharing starts (the
edit code then never shows) and while sharing. collab.share {via: "local",
access: "view"} does the same; share in collab.peers lists both codes and
links. There is still no comment code on the local network.

Next::Stop boxes its event: SessionInfo grew past clippy's
large_enum_variant threshold, and it only ever carries a status or an end.

Tests: loopback_tests::a_view_token_lets_people_look_and_not_edit,
code_tests::a_local_view_code_lets_people_look_and_not_edit,
live_share_tests::a_local_share_can_start_for_looking_only, and the local
share tests that asserted edit-only codes. Docs: collab-protocol.md,
collaboration-parity.md.
2026-11-06 17:03:21 +01:00
FredApps 0b97f3f180 Merge pull request #2040 from storytold/house-rules
Copy the shared house rules into AGENTS.md
2026-11-06 14:59:45 +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 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
FredApps 0097667e24 Copy the shared house rules into AGENTS.md.
plan/ is an optional notebook, the calendar exception and the Rust-only line now live in the Rust rules, and the quality gates no longer say to push to main. The Figma iconography ban stays. CLAUDE.md imports AGENTS.md.
2026-11-06 11:42:42 +01:00
Denis GomesandClaude Opus 5.5 6a31ea7360 M3: Go to main component names a library copy with no link
A file imported from another app can hold library copies in its library
store with no record of the library they came from. On an instance of
one, the command took the local path and failed in selecting it with
"isn't on a page (it is a library component)", which says nothing about
what to do. It now says the copy has no link to its library and that
enabling the library reaches it.

Found on a real imported file, where copies of the same library set were
cached more than once and only one of them could be linked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 02:53:58 -05:00
Denis GomesandClaude Opus 5.5 bea214ebb0 M7: decode a clipboard PNG before pasting it
with_pixel_size read only the PNG's header, so a PNG with a whole IHDR and
a cut-off body passed as the one to paste. Since #1724 image.place refuses
it as damaged, and nothing was pasted: the decoded clipboard image, which
paste falls back to for an unreadable PNG, was never tried. The PNG is now
decoded in full before it is accepted, so the paste falls back as it does
for any other unreadable PNG. The zero-size and pixel-count checks run
ahead of that decode, so an image past the limit is refused before it is
decoded.

Raised by CodeRabbit on #1360 after the merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 02:37:04 -05:00
Denis GomesandClaude Opus 5.5 7076ea2131 Credits: my name in the About window
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 02:27:21 -05:00