mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-13 05:42:38 +00:00
M4: Boolean groups: test the one-radius panel through corner smoothing (#2222)
The field test's "no field per corner" check passed for any shape, since corner fields are built only while the individual-corners popover is open. A boolean group's button now has its own test beside the polygons' (shared helper): it is named Corner smoothing, opens no corner fields, and sets the smoothing in one undo step. ROADMAP's status line reads minor, as the other docs do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Model-Effort: Opus 5.5 drove and wrote; Fable 5.1 reviewed
This commit is contained in:
1 parent
529134b850
commit
bae366ba51
3 files changed
+34
-18
No files matched your search
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
**Stage: alpha** · next: beta, ~26 points and ~162–292 h away (one Opus 5.5 agent; ~33–67 h wall-clock with 5 agents)
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-10 · **Change:** patch (a boolean group takes a corner radius (and smoothing) that rounds every sharp corner of its result (#2222); the handoff mode reads Developer Mode in the interface, its ids and saved fields unchanged; the left rail's and tab strip's icons are larger, 20 pt (#553); Suggest auto layout adds nested auto layout frames by rows and columns and keeps the placement of the design, as the help center describes (#1823); the Local variables window selects several variables (click, Shift+click for a range, Cmd/Ctrl+click to toggle, Escape to clear) and delete, move to a group and scope act on the selected local variables in one undo step (#2002); the desktop UI takes a system face for Ethiopic, Devanagari, Gurmukhi, Gujarati, Tamil, Telugu, Kannada and Malayalam, Sinhala, Lao, Myanmar, Khmer, Armenian and Georgian as it did for Hebrew and Arabic, so a String variable's Amharic value draws in the panels instead of boxes (#2014); a layer's effect binding takes only a field its effect has; ; the boolean operation menu leads each entry with an original icon (#2003); glass takes one sample instead of its dispersion window where the colours split under 1/256 px; glass bends towards its core, and rounded rectangles work it out in closed form; glass's light is a line on its bevel and splay turns the bend towards the centre; glass bends with a thick-lens profile and splits its colours over 8 steps; glass effects take variables and keep their frost within 0–100; web host bridge: `?host=parent` or `data-host="page"` hands a page the app's files, documented in the hosting guide; a connector end moved with Cmd/Ctrl held goes on any part of an object, and stays there as the object moves or resizes; a tap on a plugin panel's button keeps what was typed in its fields; a Dash cap setting (none, round or square) on Dash and Custom strokes, drawn on both ends of every dash; library analytics list how many layers and files use each style and variable; the Controller menu names each button as PlayStation and Switch controllers do; widgets draw lines and SVG pictures (plugin API 6); a connector end dropped along a side of a shape attaches to that side (#1708); regression tests pin a rounded shape's one-sided stroke and SVG ids and font families without XML-forbidden characters (#1351, #1124); a damaged image is refused with a reason instead of showing as an empty box (#1474); an image copied in another app pastes again on the desktop, and in the browser for the first time (#492); SwiftUI and Compose code names a bound variable by its iOS or Android code syntax; the colour picker's Image tab shows a placeholder and Upload from computer, an asset preview, and a file dropped on it replaces the image, keeping the fill mode, crop and position (#1682); the Frame tool's Phone presets have the iPhone 17 family (#1691); Show in folder works in the Flatpak build and selects the file on Linux (#1701); a dashed line starts and ends with a half-length dash, on the canvas and in exports; the Pages, Layers and Assets search fields keep their text in the middle of the field (#1703); a board connector's path can be reshaped by dragging its path handles (#1710); boards have 16 more shapes (arrows, polygons, star, plus and flowchart symbols), in pickers that lay them out in a grid; library analytics count insertions and show insertions and detachments over the past 30 days, 90 days or year; a stroke's Custom style takes a dash pattern of any length as a comma-separated list (#1681); a control request can set its own timeout, up to 10 minutes (`timeoutMs`; `figcraft-cli mcp --attach PORT --timeout SECONDS`) (#1696); gap 6: a widget opens its plugin's panel (plugin API 5), whose callbacks redraw it; the keyboard's Menu key opens the context menu, as Shift+F10 does; an Open link action can open in place of the prototype, with "Open in new tab" off (gap 15); a collection of this file can be extended (Extend collection on its row, `variable.extendCollection`), its extension following its edits at once, and extended collections extend again in the file; the eyedropper with Cmd/Ctrl+Shift makes a colour style or variable of what it picks; a live session's welcome sends the document compressed to clients that read it, about seven times smaller (#1665); a control request that times out says truthfully whether it was dropped or is still running and will take effect, and only screenshots and input blame the window (#1673); a board keeps earlier votes' results, listed newest first in Results and deleted one at a time; the object a stamp will stick to is outlined while the stamp tool is over it or a stamp is dragged over it, and board objects take no drops (gap 24); a server can list the files shared from it now for everyone who reaches it, Home ▸ Online shows them and their links carry no secret (`team_files`, part of #742); team library analytics count detached instances per componeLine truncated
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-10 · **Change:** minor (a boolean group takes a corner radius (and smoothing) that rounds every sharp corner of its result (#2222); the handoff mode reads Developer Mode in the interface, its ids and saved fields unchanged; the left rail's and tab strip's icons are larger, 20 pt (#553); Suggest auto layout adds nested auto layout frames by rows and columns and keeps the placement of the design, as the help center describes (#1823); the Local variables window selects several variables (click, Shift+click for a range, Cmd/Ctrl+click to toggle, Escape to clear) and delete, move to a group and scope act on the selected local variables in one undo step (#2002); the desktop UI takes a system face for Ethiopic, Devanagari, Gurmukhi, Gujarati, Tamil, Telugu, Kannada and Malayalam, Sinhala, Lao, Myanmar, Khmer, Armenian and Georgian as it did for Hebrew and Arabic, so a String variable's Amharic value draws in the panels instead of boxes (#2014); a layer's effect binding takes only a field its effect has; ; the boolean operation menu leads each entry with an original icon (#2003); glass takes one sample instead of its dispersion window where the colours split under 1/256 px; glass bends towards its core, and rounded rectangles work it out in closed form; glass's light is a line on its bevel and splay turns the bend towards the centre; glass bends with a thick-lens profile and splits its colours over 8 steps; glass effects take variables and keep their frost within 0–100; web host bridge: `?host=parent` or `data-host="page"` hands a page the app's files, documented in the hosting guide; a connector end moved with Cmd/Ctrl held goes on any part of an object, and stays there as the object moves or resizes; a tap on a plugin panel's button keeps what was typed in its fields; a Dash cap setting (none, round or square) on Dash and Custom strokes, drawn on both ends of every dash; library analytics list how many layers and files use each style and variable; the Controller menu names each button as PlayStation and Switch controllers do; widgets draw lines and SVG pictures (plugin API 6); a connector end dropped along a side of a shape attaches to that side (#1708); regression tests pin a rounded shape's one-sided stroke and SVG ids and font families without XML-forbidden characters (#1351, #1124); a damaged image is refused with a reason instead of showing as an empty box (#1474); an image copied in another app pastes again on the desktop, and in the browser for the first time (#492); SwiftUI and Compose code names a bound variable by its iOS or Android code syntax; the colour picker's Image tab shows a placeholder and Upload from computer, an asset preview, and a file dropped on it replaces the image, keeping the fill mode, crop and position (#1682); the Frame tool's Phone presets have the iPhone 17 family (#1691); Show in folder works in the Flatpak build and selects the file on Linux (#1701); a dashed line starts and ends with a half-length dash, on the canvas and in exports; the Pages, Layers and Assets search fields keep their text in the middle of the field (#1703); a board connector's path can be reshaped by dragging its path handles (#1710); boards have 16 more shapes (arrows, polygons, star, plus and flowchart symbols), in pickers that lay them out in a grid; library analytics count insertions and show insertions and detachments over the past 30 days, 90 days or year; a stroke's Custom style takes a dash pattern of any length as a comma-separated list (#1681); a control request can set its own timeout, up to 10 minutes (`timeoutMs`; `figcraft-cli mcp --attach PORT --timeout SECONDS`) (#1696); gap 6: a widget opens its plugin's panel (plugin API 5), whose callbacks redraw it; the keyboard's Menu key opens the context menu, as Shift+F10 does; an Open link action can open in place of the prototype, with "Open in new tab" off (gap 15); a collection of this file can be extended (Extend collection on its row, `variable.extendCollection`), its extension following its edits at once, and extended collections extend again in the file; the eyedropper with Cmd/Ctrl+Shift makes a colour style or variable of what it picks; a live session's welcome sends the document compressed to clients that read it, about seven times smaller (#1665); a control request that times out says truthfully whether it was dropped or is still running and will take effect, and only screenshots and input blame the window (#1673); a board keeps earlier votes' results, listed newest first in Results and deleted one at a time; the object a stamp will stick to is outlined while the stamp tool is over it or a stamp is dragged over it, and board objects take no drops (gap 24); a server can list the files shared from it now for everyone who reaches it, Home ▸ Online shows them and their links carry no secret (`team_files`, part of #742); team library analytics count detached instances per componeLine truncated
|
||||
|
||||
FigCraft is a clean-room, open-source, pure-Rust reimplementation of the Figma design workflow,
|
||||
native on macOS, Windows, Linux and FreeBSD and in the browser, fully drivable by agents through
|
||||
|
||||
@@ -166,25 +166,42 @@ fn polygons_and_stars_take_corner_smoothing() {
|
||||
for cmd in ["shape.polygon", "shape.star"] {
|
||||
let mut s = Session::new();
|
||||
let id = NodeId(s.execute(cmd, &json!({"x": 0, "y": 0, "width": 120, "height": 120})).unwrap()["id"].as_u64().unwrap());
|
||||
s.execute("node.set", &json!({"ids": [id], "props": {"cornerRadius": 8}})).unwrap();
|
||||
s.execute("select.set", &json!({"ids": [id]})).unwrap();
|
||||
let mut u = Ui::new(s);
|
||||
// With no corners of their own, the button is named for what it opens.
|
||||
let t = named_button(&mut u, "Corner smoothing");
|
||||
click(&mut u, t);
|
||||
assert!((0..4).all(|k| u.ctx.read_response(Id::unique(("corner", k)).with("field")).is_none()), "{cmd}: one radius, no corner fields");
|
||||
let smoothing = u.ctx.read_response(Id::unique("smoothing").with("field")).expect("the smoothing field shows").rect.center();
|
||||
let history = u.app.session.undo_depth().0;
|
||||
click(&mut u, smoothing);
|
||||
u.frame(vec![Event::Text("60".into())]);
|
||||
enter(&mut u);
|
||||
u.frame(vec![]);
|
||||
let n = u.app.session.doc().unwrap().node(id).unwrap().clone();
|
||||
assert!((n.corners.smoothing - 0.6).abs() < 1e-9, "{cmd}: {}", n.corners.smoothing);
|
||||
assert_eq!(u.app.session.undo_depth().0, history + 1, "{cmd}: one undo step");
|
||||
takes_corner_smoothing(s, id, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
/// A boolean group has one radius for its whole result (#2222): like a polygon, its button opens
|
||||
/// corner smoothing, with no field per corner.
|
||||
#[test]
|
||||
fn a_boolean_group_opens_corner_smoothing_and_has_no_corner_fields() {
|
||||
let mut s = Session::new();
|
||||
let rect = |s: &mut Session, x: i32| s.execute("shape.rectangle", &json!({"x": x, "y": 0, "width": 100, "height": 100})).unwrap()["id"].clone();
|
||||
let (a, b) = (rect(&mut s, 0), rect(&mut s, 60));
|
||||
let g = NodeId(s.execute("bool.union", &json!({"ids": [a, b]})).unwrap()["id"].as_u64().unwrap());
|
||||
takes_corner_smoothing(s, g, "bool.union");
|
||||
}
|
||||
|
||||
/// With `id` rounded at 8 and selected: the panel's button is named Corner smoothing and opens
|
||||
/// the smoothing field with no field per corner, and typing 60 sets the smoothing in one undo step.
|
||||
fn takes_corner_smoothing(mut s: Session, id: NodeId, what: &str) {
|
||||
s.execute("node.set", &json!({"ids": [id], "props": {"cornerRadius": 8}})).unwrap();
|
||||
s.execute("select.set", &json!({"ids": [id]})).unwrap();
|
||||
let mut u = Ui::new(s);
|
||||
// With no corners of their own, the button is named for what it opens.
|
||||
let t = named_button(&mut u, "Corner smoothing");
|
||||
click(&mut u, t);
|
||||
assert!((0..4).all(|k| u.ctx.read_response(Id::unique(("corner", k)).with("field")).is_none()), "{what}: one radius, no corner fields");
|
||||
let smoothing = u.ctx.read_response(Id::unique("smoothing").with("field")).expect("the smoothing field shows").rect.center();
|
||||
let history = u.app.session.undo_depth().0;
|
||||
click(&mut u, smoothing);
|
||||
u.frame(vec![Event::Text("60".into())]);
|
||||
enter(&mut u);
|
||||
u.frame(vec![]);
|
||||
let n = u.app.session.doc().unwrap().node(id).unwrap().clone();
|
||||
assert!((n.corners.smoothing - 0.6).abs() < 1e-9, "{what}: {}", n.corners.smoothing);
|
||||
assert_eq!(u.app.session.undo_depth().0, history + 1, "{what}: one undo step");
|
||||
}
|
||||
|
||||
/// A closed vector shows the same Corner smoothing control, and its iOS button sets 60% in one
|
||||
/// undo step; an open vector, which has no closed shape to smooth, shows no smoothing control.
|
||||
#[test]
|
||||
|
||||
@@ -1416,7 +1416,6 @@ fn a_boolean_group_has_one_corner_radius_field_and_a_mixed_selection_reads_mixed
|
||||
let radii = |u: &Ui, n: NodeId| u.app.session.doc().unwrap().node(n).unwrap().corners.radii;
|
||||
assert_eq!(radii(&u, g), [20.0; 4]);
|
||||
assert_eq!(undo_steps(&u), steps + 1, "one undo step");
|
||||
assert!((0..4).all(|k| u.ctx.read_response(Id::unique(("corner", k)).with("field")).is_none()), "no field per corner");
|
||||
u.app.run("select.set", json!({"ids": [g, r]}));
|
||||
assert_eq!(field(&mut u, "Corner radius").1, "Mixed");
|
||||
type_into(&mut u, "Corner radius", "8", Key::Enter);
|
||||
|
||||
Reference in new issue
Block a user