Commit Graph
1442 Commits
Author SHA1 Message Date
bflatastic 1e1b59280d Merge main into #849 2026-10-19 22:07:53 +07:00
bflatastic 918a28a4f4 Merge main into #848 2026-10-19 22:07:51 +07:00
bflatastic 97cd06fd14 Merge main into #842 2026-10-19 22:07:50 +07:00
bflatastic ee99a89e08 Merge main into #817 2026-10-19 22:07:49 +07:00
bflatastic 9c7b315301 Merge main into #814 2026-10-19 22:07:48 +07:00
bflatastic b55f009198 Merge main into #811 2026-10-19 22:07:45 +07:00
bflatastic ca3832c0d9 Merge main into #807 2026-10-19 22:07:44 +07:00
叶林橦 3e02bffbbc Layout grids: Auto line width for columns and rows
Columns and rows can now set their line width to Auto: a width of 0 (or
typing the word "Auto" / "自动" in the field) draws a one-pixel hairline
down the band's middle, like the square grid, instead of a filled band.

- grid_lines: a non-stretched grid with size <= 0 emits a hairline at each
  band's middle rather than a zero/negative-width rect.
- canvas: a degenerate (zero-width or zero-height) grid rect is painted as a
  1px line; a filled band is still painted as a rectangle.
- number_field: a field whose placeholder is "Auto" also accepts the word
  "Auto"/"自动", mapping it to 0.
- right: the grid size field shows "Auto" and the align menu no longer
  forces a 60px band on a zero width.
- props: document the Auto semantics of LayoutGrid::size.
- ROADMAP.md / docs/ui-parity.md: note the change.
- layout tests: regression test for the Auto hairline.
2026-10-19 22:51:57 +08:00
YarilaFox 4b12a82839 M7: refresh FigCraft brand colors and wordmark 2026-10-19 19:43:58 +05:00
NetterSilverandClaude Haiku 5.5 258d6f61c0 Reject non-hex characters in live::parse_color
Found while testing crates/engine/src/live.rs:85

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 14:49:19 +02:00
NetterSilverandClaude Haiku 5.5 df42e380f2 Reject non-hex characters inside invite link percent escapes
Found while testing crates/collab-net/src/invite.rs:242 (percent_decode)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 12:37:21 +00:00
Davit MatchakhelidzeandClaude Opus 5.5 d2367d1eac Web: offer Georgian only when Noto Sans Georgian is built in; ROADMAP lists the Georgian draft
Without craft-fonts the web build has no Georgian face and can't read installed fonts, so the
language picker and System must skip Georgian there, as they already skip Japanese, Korean and
Chinese without their faces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-19 16:35:09 +04:00
NetterSilverandClaude Haiku 5.5 ccddaa74cd Clamp overflowing colour channels to 1 instead of 0
Found while testing crates/color/src/lib.rs:63 (clamp01, used by Color::new via parse_css at :155-166)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 12:27:23 +00:00
justinjohn0306andClaude Opus 5.5 82a50dad1b M7: a preference to start on the Home screen (#857)
Launching opens a new empty file, which then has to be closed to get
back to Home. Preferences ▸ "Start on the Home screen"
(`pref.startOnHome {on?}`, off by default) starts the desktop app on Home
instead. Files named on the command line and recovered files still open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 17:53:24 +05:30
justinjohn0306andClaude Opus 5.5 6a163721dc M3: explicit edits of bound scalar fields detach their variable (#809)
An explicit edit of a bound width, height, opacity, gap, padding, corner
radius, stroke weight, arc or text field reported ok, but normalize wrote
the variable's value straight back. Now the edit sticks and that field's
binding is dropped, as paint edits already do.

- `styles::bound_before` / `detach_edited`: compare each layer-level
  bound field's stored value before and after the edit (`bound_field`),
  so keys written unchanged keep their bindings and fields moved along
  with another (proportions, min/max) lose theirs. A binding the edit
  itself made (`boundVariables`) stays. List and component property
  bindings are left to their own rules.
- Hooked into node.set, transform.resize, object.opacityKeys, text.style
  and `Session::preview` (canvas drags of the selected layers).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 17:33:29 +05:30
Davit MatchakhelidzeandClaude Opus 5.5 41c123657e i18n: Georgian (ka) interface language
Adds crates/ui-egui/locales/ka.toml (ქართული): every key of en.toml (1,730 UI
strings), every command label (567) and every menu wording (31), marked as a
draft. Georgian has no letter case and keeps a noun singular after a numeral,
so plural entries give the same text for one and other. System picks it for
ka, ka-GE and ka_GE.UTF-8; pref.language's help text lists ka.

Inter and egui's fonts have no Georgian, so the UI draws it in craft-fonts'
Noto Sans Georgian: it joins the built-in faces in theme::install_fonts and
the web build keeps it (crates/text/build.rs). craft-fonts-ref moves to
a71c20e3 (storytold/craft-fonts#27, the commit that adds the face) in the
release and FreeBSD workflows, and the release builds the web version with
craft-fonts (web-craft-fonts: true).

Tests: the Georgian locale tags, draft status and count-neutral plurals, and
that the built-in Georgian face draws every Georgian letter in the catalog
(skipped without craft-fonts). Docs: localization-parity, translating, mcp,
README and ROADMAP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-19 15:53:03 +04:00
MortenHustedandCodex 0108a7c2f5 M3: copied components paste as instances within their file
Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
2026-10-19 13:34:40 +02:00
NetterSilverandClaude Haiku 5.5 b7c9c712b1 Reject server addresses whose port is not all ASCII digits
Found while testing crates/collab-net/src/invite.rs:valid_host (around line 205)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 11:21:34 +00:00
AganzeFelicite 710c95e527 test(text): a selection across a line separator shows the break (#852) 2026-10-19 12:52:09 +02:00
AganzeFelicite 6e7b63bc16 fix(pages): Delete that removes an interaction ends the Pages list focus 2026-10-19 12:50:43 +02:00
AganzeFelicite 3e83ae44b7 fix(pages): a prototype gesture on the canvas ends the Pages list focus 2026-10-19 12:50:43 +02:00
dependabot[bot] 40d7ce14ce ci(deps): bump actions/checkout
Bumps the actions group with 1 update in the / directory: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 4 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-19 10:18:38 +00:00
AganzeFelicite b930ea5871 test(text): the caret's line on either side of a line separator (#852) 2026-10-19 12:13:12 +02:00
AganzeFelicite 1b66fec3ec fix(text): the caret after a line break sits on the next line
`caret` put the index right after a line break (a newline, or a line
separator) at the end of the line above, so after Enter the caret showed
on the previous line until something was typed, and Up/Down counted
lines from there. The index before the break stays at the end of its
line; the one after it is now on the next line, as after a wrap.
Selection highlights already started such lines at their own start
(#665); their comment no longer describes the old caret.
2026-10-19 12:12:54 +02:00
AganzeFelicite e17fccd7ab fix(pages): a second Delete after deleting layers keeps the page
After a click in the Pages list, layers selected since (Select all, say)
took the first Delete, which left nothing selected, so a second Delete
deleted the page. Delete that doesn't take the page now ends the Pages
list's hold on the keyboard.
2026-10-19 11:50:07 +02:00
AganzeFelicite 9f895a37d5 fix(pages): Delete deletes no page once the Pages list is off screen
The Pages list kept the keyboard after a switch to the Assets tab or to
another open file, so Delete with nothing selected deleted a page there.
It now deletes a page only while the list that was clicked is on screen
(File tab, list expanded, panel and interface shown) and its file is the
one open.
2026-10-19 11:48:35 +02:00
AganzeFelicite 7af44512e7 refactor(pages): remember which file the Pages list focus belongs to 2026-10-19 11:46:47 +02:00
AganzeFelicite 9e4dcc9204 test(text): line separators in layout, lists and carets (#852) 2026-10-19 11:34:58 +02:00
AganzeFelicite 692f45123c fix(text): a line separator breaks the line inside its paragraph (#852)
Figma stores Shift+Enter as U+2028. `.fig` imports keep it, but layout
only broke lines at a newline, so imported text ran soft-broken lines
together ("Option 1SL: …"), lost the empty line of a separator followed
by a newline, and put glyph bullets on one line.

A line separator now ends its line without ending the paragraph: no
paragraph spacing and no new list marker after it, the next line keeps
the item's indent, and it draws no glyph. A trailing one leaves an empty
last line, and a line it cuts short isn't stretched when justified.
2026-10-19 11:34:47 +02:00
AganzeFelicite 855bbb328f refactor(text): Line::break_after for carets and selections
Carets, clicks and selections asked `para_end && !last_line` to mean
"the line ends with a break char". Record that on the line instead, so a
break that doesn't end the paragraph can use the same rules. No
behaviour change: today only a newline ends a line early.
2026-10-19 11:34:34 +02:00
AganzeFelicite fd02772d22 refactor(text): one helper for the chars that end a line 2026-10-19 11:34:21 +02:00
YarilaFox 2af54a26fb M7: keep tab strip usable with many files 2026-10-19 14:23:04 +05:00
YarilaFox d2dfe2ef20 M7: preserve active tab and support tab reordering 2026-10-19 14:21:52 +05:00
AganzeFelicite 139e0ba308 chore(contributors): add AganzeFelicite to people.toml 2026-10-19 10:53:34 +02:00
AganzeFelicite 5d40b3f42f fix(pages): Delete deletes the page picked in the Pages list (#859)
Clicking a page row (or the add-page button) gives the Pages list the
keyboard: Delete or Backspace with no layers selected now deletes the
current page instead of failing with "nothing is selected". No
confirmation; a "Page deleted" toast says so and Undo brings it back.
The context menu's Delete page shows the same toast. Selecting layers,
clicking a layer row or pressing on the canvas gives Delete back to the
layers.
2026-10-19 10:53:34 +02:00
slyimandClaude Opus 5.5 be212f00e6 #826: the stroke-width test covers the Frame and Text marks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 11:25:34 +03:00
MortenHustedandClaude Opus 5.5 be9e72f74d M3: instances follow the variable modes their main component sets
An instance copied its main's variable modes when it was made and kept
the copy as modes of its own. Switching it to another variant kept the
old variant's modes, and a mode set on the main later never reached it.

Instances now take the modes their main sets through the link
(`Document::inherited_modes`), closer than any frame around them. Modes
set on the instance itself still win and stay through variant switches.
Bindings are re-applied for the instances of a main that chose another
mode and for an instance that switched mains. Detaching keeps the modes
the instance took from its main, and the Variable modes panel shows them
as the instance's Auto value.

Instances made before this change keep their copy as modes of their
own; setting one back to Auto lets it follow its main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 09:15:34 +02:00
AJ Richardson 3b7b28c03c improve page search functionality to close with Escape key even when unfocused 2026-10-19 16:37:52 +10:00
AJ Richardson caf0f44d90 #855: The Search button for the pages doesn't work as expected 2026-10-19 16:08:26 +10:00
Cody Campbell 4e4bf54bb6 M1: smart-selection drag reordering 2026-10-18 18:53:32 -04:00
MortenHustedandClaude Opus 5.5 6f90717e71 M3: effects refuse keys they don't read instead of dropping them
node.set refuses unknown properties, but the effects inside it were read
by serde, which drops keys it doesn't know. A drop shadow given
offsetX/offsetY (the names variable bindings use for its offset) was
stored with the default offset [0, 4] and the call said ok. effect.add
and effect.set dropped them the same way.

The three now refuse what the effect doesn't read and say what it
takes: "effects[0]: unknown properties: offsetX, offsetY (a dropShadow
takes blend, color, offset, radius, showBehind, spread, type, visible)".
A key counts as read when setting it to null makes the effect fail to
parse or come out different, so serde's aliases count as read without a
list of them here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:53:05 +02:00
MortenHustedandClaude Opus 5.5 147a374350 M3: a refused slot names component.makeSlot
component.makeSlot adds its slot property through add_prop, which named
itself in every error: making a second slot called "Content" said
"invalid parameters for `component.addProperty`: a property named
`Content` already exists", a command the caller never ran. add_prop now
takes the command errors name, the fix #807 makes for wrap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:53:04 +02: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 f97789ade1 M7: CSS borders follow individual stroke sides
A layer with individual stroke sides (a header with only a bottom divider,
strokeSides [0, 0, 1, 0]) came out of the CSS, HTML, React and Leptos
code as `border: 1px solid ...` on all four sides, because the border
was written from the stroke weight alone. With individual sides it is now
a border-top/right/bottom/left for each side that has a weight, and a
side bound to a variable names it (border-top: var(--border-top, 3px)).

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 544b2455c9 M8: a path in a root reached through a link is told why it is refused
When a path can't be resolved (a link that leads nowhere, `..` below a
missing folder), the reason is given only for paths inside a root.
That check compared the path as typed with the roots resolved, so a
root reached through a link (`/tmp/work` on macOS is
`/private/tmp/work`) called its own dangling links "outside the folders
that may be written: /private/tmp/work", and a link out of a root still
had its reason told. The existing part of the path is now resolved
before the comparison, as the roots were.

This is also why `links_that_lead_out_of_a_root_are_outside` and
`outside_paths_are_refused_alike_whatever_exists_there` failed on
macOS, where the temp folder is under `/var` -> `/private/var`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:53:03 +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
Muamer MustaficandCursor 63dc418cc2 M7: a web file picker closed without a cancel event settles on refocus (#759)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-18 23:50:27 +02:00
Al Rifat Sabbir 847e610550 Add Bengali script support in build.rs 2026-10-19 03:46:01 +06:00
Muamer MustaficandCursor 79ac16a256 M3: doc comments for the structure commands and binding re-apply (#810)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-18 23:40:35 +02:00