diff --git a/crates/ui-egui/src/corner_individual_tests.rs b/crates/ui-egui/src/corner_individual_tests.rs index c87f4f339..e76f2f57e 100644 --- a/crates/ui-egui/src/corner_individual_tests.rs +++ b/crates/ui-egui/src/corner_individual_tests.rs @@ -70,6 +70,75 @@ fn ios_button_sets_corner_smoothing_to_60_percent() { assert_eq!(u.app.session.undo_depth().0, history + 1); } +/// The visible smoothing icon uses the same scrub gesture, cursor and modifier rates as the +/// other numeric fields; the whole drag is one undo step, including when clamped at either end. +#[test] +fn corner_smoothing_icon_scrubs_with_modifiers_and_one_undo_step() { + for (dx, modifiers, want) in + [(16.0, Modifiers::NONE, 0.66), (8.0, Modifiers::SHIFT, 1.0), (16.0, Modifiers::ALT, 0.516), (-80.0, Modifiers::NONE, 0.0)] + { + let (mut u, r) = scene(); + u.app.run("node.set", json!({"ids": [r], "props": {"cornerSmoothing": 0.5}})); + let toggle = named_button(&mut u, "Individual corners"); + click(&mut u, toggle); + let lead = u.ctx.read_response(Id::unique("smoothing").with("lead")).expect("smoothing icon").rect; + assert_eq!(lead.width(), 24.0, "the icon has the same drag target as other fields"); + let from = lead.center(); + let out = u.frame_output(vec![Event::PointerMoved(from)]); + assert_eq!(out.platform_output.cursor_icon, egui::CursorIcon::ResizeHorizontal); + let history = u.app.session.undo_depth().0; + u.frame_with(vec![Event::PointerButton { pos: from, button: egui::PointerButton::Primary, pressed: true, modifiers }], modifiers); + for step in 1..=4 { + u.frame_with(vec![Event::PointerMoved(pos2(from.x + dx * step as f32 / 4.0, from.y))], modifiers); + } + let smoothing = |u: &Ui| u.app.session.doc().unwrap().node(r).unwrap().corners.smoothing; + assert!((smoothing(&u) - want).abs() < 1e-9, "the drag updates the canvas before release"); + u.frame_with( + vec![Event::PointerButton { pos: pos2(from.x + dx, from.y), button: egui::PointerButton::Primary, pressed: false, modifiers }], + modifiers, + ); + u.frame(vec![]); + assert!((smoothing(&u) - want).abs() < 1e-9, "release keeps the scrubbed value"); + assert_eq!(u.app.session.undo_depth().0, history + 1); + u.app.session.undo().unwrap(); + assert_eq!(smoothing(&u), 0.5); + u.app.session.redo().unwrap(); + assert!((smoothing(&u) - want).abs() < 1e-9); + } +} + +/// Fine drags keep their fractional baseline across gestures, including a later plain drag. +#[test] +fn consecutive_smoothing_drags_preserve_fractional_percentages() { + let (mut u, r) = scene(); + u.app.run("node.set", json!({"ids": [r], "props": {"cornerSmoothing": 0.5}})); + let toggle = named_button(&mut u, "Individual corners"); + click(&mut u, toggle); + let history = u.app.session.undo_depth().0; + let smoothing = |u: &Ui| u.app.session.doc().unwrap().node(r).unwrap().corners.smoothing; + for (step, (dx, modifiers, want)) in + [(16.0, Modifiers::ALT, 0.516), (16.0, Modifiers::ALT, 0.532), (8.0, Modifiers::NONE, 0.612)].into_iter().enumerate() + { + let from = u.ctx.read_response(Id::unique("smoothing").with("lead")).unwrap().rect.center(); + u.frame_with(vec![Event::PointerMoved(from)], modifiers); + u.frame_with(vec![Event::PointerButton { pos: from, button: egui::PointerButton::Primary, pressed: true, modifiers }], modifiers); + for k in 1..=4 { + u.frame_with(vec![Event::PointerMoved(pos2(from.x + dx * k as f32 / 4.0, from.y))], modifiers); + } + u.frame_with( + vec![Event::PointerButton { pos: pos2(from.x + dx, from.y), button: egui::PointerButton::Primary, pressed: false, modifiers }], + modifiers, + ); + u.frame(vec![]); + assert!((smoothing(&u) - want).abs() < 1e-9, "gesture {step}: expected {want}, got {}", smoothing(&u)); + assert_eq!(u.app.session.undo_depth().0, history + step + 1); + } + for want in [0.532, 0.516, 0.5] { + u.app.session.undo().unwrap(); + assert!((smoothing(&u) - want).abs() < 1e-9, "undo restores the precise value"); + } +} + /// The fields sit at their corners, and typing in the bottom right one sets only that corner in a /// single undo step (#1002: the bottom row showed bottom right on the left). #[test] diff --git a/crates/ui-egui/src/icons.rs b/crates/ui-egui/src/icons.rs index eeacb2756..a891abed4 100644 --- a/crates/ui-egui/src/icons.rs +++ b/crates/ui-egui/src/icons.rs @@ -89,6 +89,8 @@ icons! { FlipH, FlipV, CornerRadius, + /// A smooth corner with its two tangent endpoints. + CornerSmoothing, /// Individual corners: the rounded corner each field sets. CornerTopLeft, CornerTopRight, diff --git a/crates/ui-egui/src/icons/draw.rs b/crates/ui-egui/src/icons/draw.rs index 7eedfdd14..953ef2de9 100644 --- a/crates/ui-egui/src/icons/draw.rs +++ b/crates/ui-egui/src/icons/draw.rs @@ -510,6 +510,18 @@ fn draw(p: &P, icon: Icon) { p.fill(&[(3.0, 9.0), (13.0, 9.0), (13.0, 14.0)]); } CornerRadius | CornerTopLeft => p.corner(2.5, 2.5, 1.0, 1.0, 11.5, 6.0), + CornerSmoothing => { + // An original superellipse corner, with dots marking its tangent endpoints. + let points: Vec<_> = (0..=24) + .map(|i| { + let a = i as f32 / 24.0 * FRAC_PI_2; + (2.5 + 11.0 * (1.0 - a.cos().max(0.0).sqrt()), 2.5 + 11.0 * (1.0 - a.sin().max(0.0).sqrt())) + }) + .collect(); + p.path(&points, false); + p.dot(2.5, 13.5, 1.0); + p.dot(13.5, 2.5, 1.0); + } CornerTopRight => p.corner(13.5, 2.5, -1.0, 1.0, 11.5, 6.0), CornerBottomRight => p.corner(13.5, 13.5, -1.0, -1.0, 11.5, 6.0), CornerBottomLeft => p.corner(2.5, 13.5, 1.0, -1.0, 11.5, 6.0), diff --git a/crates/ui-egui/src/panels/right/paints.rs b/crates/ui-egui/src/panels/right/paints.rs index a24fca55b..095649ec7 100644 --- a/crates/ui-egui/src/panels/right/paints.rs +++ b/crates/ui-egui/src/panels/right/paints.rs @@ -113,10 +113,10 @@ pub(super) fn appearance(app: &mut FigApp, ui: &mut Ui, doc: &Document, sel: &[N } }); } - let sm = common(doc, sel, |n| (n.corners.smoothing * 100.0).round()); + let sm = common(doc, sel, |n| n.corners.smoothing * 100.0); widgets::sublabels(ui, tr("Corner smoothing"), None); row(ui, 32.0, |ui| { - let e = widgets::number_field(ui, Id::unique("smoothing"), Lead::None, sm, "%", col, 1.0); + let e = widgets::number_field(ui, Id::unique("smoothing"), Lead::Icon(Icon::CornerSmoothing, tr("Corner smoothing")), sm, "%", col, 1.0); app.field(&ids, "cornerSmoothing", e, |v| json!((v / 100.0).clamp(0.0, 1.0))); if widgets::button(ui, tr("iOS"), false, true).on_hover_text(tr("Set corner smoothing to 60%")).clicked() { app.set_props(&ids, json!({"cornerSmoothing": 0.6})); diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 440e67bbf..e0a07b1d1 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI and interaction parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (guides snap to layers when dragged; Crop image: Object ▸ Crop image, the right-click menu and the image fill row enter crop mode (`image.crop`, #1464); the Design panel's sections on one grid — 16 / 8 margins, 88 + 8 + 88 + 8 + 24 columns, 24 pt rows, 4 pt between a row's buttons (#858); the toolbar on one 32 pt grid, without its Resources button (#1210); colour picker LCH and OKLCH models; an XML (Android Vector Drawable) export format; Context menus: the Menu key reaches egui 0.37 but is unbound (row done → partial); the Text on a path tool; board tool keys pinned; community fixes: the text box by dragging, the font picker keys, the pen path on leaving Design, a still canvas in vector edit mode, a scrolling pages list, colour emoji, readable new text in frames, system theme on first run; the Small caps sample in Type settings drawn from capitals; recent files as cards; vector edit mode: hovering, hatching, selecting and dragging filled regions; home screen: Templates tab, first-run templates and Getting started tips; number fields: scrub rates, arrow steps, arithmetic with brackets, relative edits of mixed values; outline mode's hidden layers and object bounds; Hide when publishing; cursors per tool and modifier; panel widths; dark pages for files made in the dark theme; drag threshold and double-clicks into text in groups; rotation origin on Alt+R and placing with Enter: no documented shortcut missing; keyboard shortcut parity suite and bindings; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and the vector edit tail; canvas navigation, guides and grids behaviour suite and fixes; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; layers panel keyboard: F2 rename, Shift+F10 context menu, F8 box selection; selection and transform behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; boolean hit testing on the result, group drags, frame outlines and slices; outlines for the selection, mask outlines; arc fields, live boolean editing, shape builder on strokes, one-sided widths; fields select their text on focus; Typography spacing icons support scrubbing (#373); CSS colour names in hex fields; the Small caps sample in Type settings drawn from capitals; recent files as cards; vector edit mode: hovering, hatching, selecting and dragging filled regions; home screen: Templates tab, first-run templates and Getting started tips; number fields: scrub rates, arrow steps, arithmetic with brackets, relative edits of mixed values; outline mode's hidden layers and object bounds; Hide when publishing; cursors per tool and modifier; panel widths; dark pages for files made in the dark theme; drag threshold and double-clicks into text in groups; rotation origin on Alt+R and placing with Enter: no documented shortcut missing; keyboard shortcut parity suite and bindings; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and the vector edit tail; canvas navigation, guides and grids behaviour suite and fixes; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; layers panel keyboard: F2 rename, Shift+F10 context menu, F8 box selection; selection and transform behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; boolean hit testing on the result, group drags, frame outlines and slices; outlines for the selection, mask outlines; arc fields, live boolean editing, shape builder on strokes, one-sided widths; a rename selects the whole name, so typing replaces it; Escape or Enter after a pen path stays on the same vector (#760); IME composition kept on through idle frames of a text edit, candidates at the caret (#747); Selection colors for mixed selections and smart-selection spacing (#753); submenus with a hover delay and a safe triangle; the Actions palette's tabs, ranked search, highlight and recents; menu items that ask for a value; editor fixes from the community batches; fields select their text on focus; Typography spacing icons support scrubbing (#373); CSS colour names in hex fields; the Small caps sample in Type settings drawn from capitals; recent files as cards; vector edit mode: hovering, hatching, selecting and dragging filled regions; home screen: Templates tab, first-run templates and Getting started tips; number fields: scrub rates, arrow steps, arithmetic with brackets, relative edits of mixed values; outline mode's hidden layers and object bounds; Hide when publishing; cursors per tool and modifier; panel widths; dark pages for files made in the dark theme; drag threshold and double-clicks into text in groups; rotation origin on Alt+R and placing with Enter: no documented shortcut missing; keyboard shortcut parity suite and bindings; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and the vector edit tail; canvas navigation, guides and grids behaviour suite and fixes; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; layers panel keyboard: F2 rename, Shift+F10 context menu, F8 box selection; selection and transform behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; boolean hit testing on the result, group drags, frame outlines and slices; outlines for the selection, mask outlines; arc fields, live boolean editing, shape builder on strokes, one-sided widths) · **Target:** Figma Design UI3 (desktop app 126.10.7) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (Corner smoothing: an original icon and the shared number-field drag gesture, preserving fractional baselines; guides snap to layers when dragged; Crop image: Object ▸ Crop image, the right-click menu and the image fill row enter crop mode (`image.crop`, #1464); the Design panel's sections on one grid — 16 / 8 margins, 88 + 8 + 88 + 8 + 24 columns, 24 pt rows, 4 pt between a row's buttons (#858); the toolbar on one 32 pt grid, without its Resources button (#1210); colour picker LCH and OKLCH models; an XML (Android Vector Drawable) export format; Context menus: the Menu key reaches egui 0.37 but is unbound (row done → partial); the Text on a path tool; board tool keys pinned; community fixes: the text box by dragging, the font picker keys, the pen path on leaving Design, a still canvas in vector edit mode, a scrolling pages list, colour emoji, readable new text in frames, system theme on first run; the Small caps sample in Type settings drawn from capitals; recent files as cards; vector edit mode: hovering, hatching, selecting and dragging filled regions; home screen: Templates tab, first-run templates and Getting started tips; number fields: scrub rates, arrow steps, arithmetic with brackets, relative edits of mixed values; outline mode's hidden layers and object bounds; Hide when publishing; cursors per tool and modifier; panel widths; dark pages for files made in the dark theme; drag threshold and double-clicks into text in groups; rotation origin on Alt+R and placing with Enter: no documented shortcut missing; keyboard shortcut parity suite and bindings; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and the vector edit tail; canvas navigation, guides and grids behaviour suite and fixes; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; layers panel keyboard: F2 rename, Shift+F10 context menu, F8 box selection; selection and transform behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; boolean hit testing on the result, group drags, frame outlines and slices; outlines for the selection, mask outlines; arc fields, live boolean editing, shape builder on strokes, one-sided widths; fields select their text on focus; Typography spacing icons support scrubbing (#373); CSS colour names in hex fields; the Small caps sample in Type settings drawn from capitals; recent files as cards; vector edit mode: hovering, hatching, selecting and dragging filled regions; home screen: Templates tab, first-run templates and Getting started tips; number fields: scrub rates, arrow steps, arithmetic with brackets, relative edits of mixed values; outline mode's hidden layers and object bounds; Hide when publishing; cursors per tool and modifier; panel widths; dark pages for files made in the dark theme; drag threshold and double-clicks into text in groups; rotation origin on Alt+R and placing with Enter: no documented shortcut missing; keyboard shortcut parity suite and bindings; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and the vector edit tail; canvas navigation, guides and grids behaviour suite and fixes; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; layers panel keyboard: F2 rename, Shift+F10 context menu, F8 box selection; selection and transform behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; boolean hit testing on the result, group drags, frame outlines and slices; outlines for the selection, mask outlines; arc fields, live boolean editing, shape builder on strokes, one-sided widths; a rename selects the whole name, so typing replaces it; Escape or Enter after a pen path stays on the same vector (#760); IME composition kept on through idle frames of a text edit, candidates at the caret (#747); Selection colors for mixed selections and smart-selection spacing (#753); submenus with a hover delay and a safe triangle; the Actions palette's tabs, ranked search, highlight and recents; menu items that ask for a value; editor fixes from the community batches; fields select their text on focus; Typography spacing icons support scrubbing (#373); CSS colour names in hex fields; the Small caps sample in Type settings drawn from capitals; recent files as cards; vector edit mode: hovering, hatching, selecting and dragging filled regions; home screen: Templates tab, first-run templates and Getting started tips; number fields: scrub rates, arrow steps, arithmetic with brackets, relative edits of mixed values; outline mode's hidden layers and object bounds; Hide when publishing; cursors per tool and modifier; panel widths; dark pages for files made in the dark theme; drag threshold and double-clicks into text in groups; rotation origin on Alt+R and placing with Enter: no documented shortcut missing; keyboard shortcut parity suite and bindings; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and the vector edit tail; canvas navigation, guides and grids behaviour suite and fixes; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; layers panel keyboard: F2 rename, Shift+F10 context menu, F8 box selection; selection and transform behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; boolean hit testing on the result, group drags, frame outlines and slices; outlines for the selection, mask outlines; arc fields, live boolean editing, shape builder on strokes, one-sided widths) · **Target:** Figma Design UI3 (desktop app 126.10.7) Fine canvas control is what Figma users feel first: handles, snapping, nudging, modifier keys, drag behaviour, numeric precision. This checklist covers the tools, gestures and panels. Status: @@ -68,7 +68,7 @@ interaction feel not yet compared. | Ctrl to suspend snapping while dragging (macOS) | done | Control on macOS (Cmd keeps working), Ctrl elsewhere; moving, resizing and drawing | | Measure (Alt hover, Cmd+Alt nested, Shift+Alt padding/gaps) | done | labels never overlap; the arrows still nudge while measuring (Shift: the big step) and the distances follow; the selection's handles and size label hide while measuring (`measure_nudge_tests`, #894) | | Pixel grid snapping, pixel preview, 2x preview | done | moves, resizes and new shapes land on whole pixels; the pixel grid shows from 400% (`view_behaviour_tests`) | -| Numeric fields: arithmetic, scrubbing by dragging the label, mixed values | done | Numeric panels use `widgets::number_field`; Typography line height, letter spacing and paragraph spacing share its leading-icon scrub logic (#373), retaining their existing unit parsing, Auto/Mixed values, variable chips and selected-character targeting (`typography_scrub_tests`). Scrubbing the label: a step a point (1 px; 1% of opacity, 1° of rotation), ten with Shift, a tenth with Alt, each stretch at the rate held while it was dragged, one undo step when let go; past the edge of the screen it goes on where the platform reports the mouse's own motion (desktop), otherwise it stops there. Arrow keys: Up/Down step a pixel field by the small nudge and Shift by the big nudge (the preferences), other fields by their own step and ten of it; they work while typing (from what is typed) and with the keyboard on the field. Typing: `+ - * /`, brackets, units (`px`, `°`, `%`, `x`, `ms`), a decimal comma; `+10`, `*2`, `/4` are relative to the value, and `50%`, `+10%`, `-10%` too in a field that isn't a percentage. Enter, Tab or a click elsewhere commits (Tab and Shift+Tab move to the next and previous control), Escape restores; a field left as it was commits nothing (no undo step, and a rounded display isn't written back). Mixed values show "Mixed": a number sets them all, a relative edit or an arrow key goes to each layer from its own value in one undo step, for X, Y, W, H, rotation, opacity, corner radius and stroke weight (elsewhere `+10` on Mixed sets 10 and `*2` does nothing) (`field_tests`). Left: scrubbing a Mixed field (nothing happens), `x2`-style suffix edits, the pointer isn't wrapped to the other side of the screen; rates not compared with Figma's | +| Numeric fields: arithmetic, scrubbing by dragging the label, mixed values | done | Numeric panels use `widgets::number_field`; Corner smoothing has an original code-drawn icon, a 24 pt drag target and an accessible name, with live changes, fractional values preserved across consecutive drags, Shift/Alt rates, 0–100% clamping and one-step undo/redo (`corner_individual_tests`); Typography line height, letter spacing and paragraph spacing share its leading-icon scrub logic (#373), retaining their existing unit parsing, Auto/Mixed values, variable chips and selected-character targeting (`typography_scrub_tests`). Scrubbing the label: a step a point (1 px; 1% of opacity, 1° of rotation), ten with Shift, a tenth with Alt, each stretch at the rate held while it was dragged, one undo step when let go; past the edge of the screen it goes on where the platform reports the mouse's own motion (desktop), otherwise it stops there. Arrow keys: Up/Down step a pixel field by the small nudge and Shift by the big nudge (the preferences), other fields by their own step and ten of it; they work while typing (from what is typed) and with the keyboard on the field. Typing: `+ - * /`, brackets, units (`px`, `°`, `%`, `x`, `ms`), a decimal comma; `+10`, `*2`, `/4` are relative to the value, and `50%`, `+10%`, `-10%` too in a field that isn't a percentage. Enter, Tab or a click elsewhere commits (Tab and Shift+Tab move to the next and previous control), Escape restores; a field left as it was commits nothing (no undo step, and a rounded display isn't written back). Mixed values show "Mixed": a number sets them all, a relative edit or an arrow key goes to each layer from its own value in one undo step, for X, Y, W, H, rotation, opacity, corner radius and stroke weight (elsewhere `+10` on Mixed sets 10 and `*2` does nothing) (`field_tests`). Left: scrubbing a Mixed field (nothing happens), `x2`-style suffix edits, the pointer isn't wrapped to the other side of the screen; rates not compared with Figma's | | Transform box at small sizes | done | a press inside a layer smaller than its handles moves it, the nearest handle wins, handles answer just outside and are hidden under 16 px on screen; cursors follow each handle and the box's rotation | | Locked layers | done | selectable from the layers panel; no handles, not dragged with the rest of a selection | | Smart selection spacing | partial | with layers in a row, a column or an aligned grid selected, the Layout section shows horizontal and vertical spacing ("Mixed" until set; typed, scrubbed, fractional and negative) and pink handles in the gaps on the canvas drag them, one undo step, Escape cancels (`arrange.spacing`; sizes, parents and offsets within each track kept; at most 2,000 layers; flow children, locked layers and layers inside instances left out). No reorder rings for swapping layers; checked by its author on macOS and in the app on Windows (#402) | @@ -174,6 +174,7 @@ in the table. `crates/ui-egui/src/shortcut_keys_tests.rs` drives the keys a look | Date | Change | Summary | |---|---|---| +| 2026-10-29 | minor | M1: Corner smoothing has an original icon and a 24 pt drag target, using the shared field gesture (live changes, fractional values preserved across consecutive drags, Shift/Alt rates, 0–100% clamping, one undo step). The field now has an accessible name (`corner_individual_tests`). Numbers unchanged | | 2026-10-29 | minor | Crop image (#1464): Object ▸ Crop image, the right-click menu and a crop button on the image fill row enter crop mode on the canvas (`image.crop`; the image dimmed outside the layer, corner brackets and edge bars on its box; Enter, Escape or a click outside finishes). The row stays done | | 2026-10-29 | minor | Toolbar (#1210): every state of the pill (tools, vector edit, shape builder, view only) is laid out on one 32 pt cell, 8 pt in from its ends and 8 pt between its items, with a 1 pt divider and 14 pt mode-switcher marks; the Resources button is gone, as in UI3 (the left rail's Assets tab, Shift+I, Alt+2 and View ▸ Panels ▸ Assets open the Assets panel); a narrow window still scrolls the pill (#1209) (`resources_tests`, `toolbar` tests). Numbers unchanged | | 2026-10-27 | minor | The Design panel's sections sit on one grid (#858): 16 pt left and 8 pt right margins, two 88 pt columns with 8 pt gutters and a 24 pt button column, 24 pt rows, 11 pt semibold section titles over 10 pt captions, 4 pt between the two buttons at a row's end; the panel's frame no longer carries a stroke (it counts as margin), so an icon's box ends 8 pt from the window edge. Checked in the app on Windows over the control channel |