From 1a68ded58138d36a7d4fd0dcc79c6de76aeaf564 Mon Sep 17 00:00:00 2001 From: Trevor Brown Date: Fri, 13 Nov 2026 01:43:34 -0500 Subject: [PATCH] M8: Tidy up keeps the second layer after a narrow first one when the gap is negative raise_negative_gap raised a negative gap from the interior widths only, so a first layer narrower than the gap's size let the second start before it (widths 10, 100 x5 at X 0, 80, 200, 260, 400, 460: the modal -40 gave X 0, -30, 30, ...). The second must start after the first, g > -w(0), in whole pixels floor(-w(0)) + 1. Figma kept the order in every measured run; a narrow first layer was not measured, so this bound keeps the order rather than follow a reading. Test: tidy_tests::a_narrow_first_layer_still_keeps_the_second_after_it. Co-Authored-By: Claude Opus 5.5 Model-Effort: Opus 5.5 drove and wrote --- crates/engine/src/selection/tidy.rs | 9 +++++++-- crates/engine/src/tidy_tests.rs | 13 +++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/selection/tidy.rs b/crates/engine/src/selection/tidy.rs index e5b9ada25..4a940e940 100644 --- a/crates/engine/src/selection/tidy.rs +++ b/crates/engine/src/selection/tidy.rs @@ -167,13 +167,18 @@ fn even_gap(gaps: &[f64], spacing: Option) -> f64 { /// in Figma's web app on 2026-11-13: interior widths of 10, 20 and 30 among 100s gave -4, -9 and /// -14, and widths of 100 left the most common gap, -40, alone. Only those three widths were /// measured; other widths follow the same formula. +/// +/// The first layer has no layer two places before it, but the second must still start after it: +/// `g > -w(0)`, in whole pixels `floor(-w(0)) + 1`. Figma kept the order in every run; a narrow +/// first layer was not measured, so this bound keeps that order rather than following a reading. fn raise_negative_gap(gap: f64, widths: &[f64]) -> f64 { let interior = widths.get(1..widths.len().saturating_sub(1)).unwrap_or(&[]); let w_min = interior.iter().copied().fold(f64::INFINITY, f64::min); - if widths.len() < 3 || !w_min.is_finite() { + let Some(first) = widths.first().copied() else { return gap }; + if widths.len() < 3 || !w_min.is_finite() || !first.is_finite() { return gap; } - gap.max((-w_min / 2.0).floor() + 1.0) + gap.max((-w_min / 2.0).floor() + 1.0).max((-first).floor() + 1.0) } /// A row (`vertical` false) or a column: the first layer stays, each next one follows the last at diff --git a/crates/engine/src/tidy_tests.rs b/crates/engine/src/tidy_tests.rs index 69c52e310..6f6034b78 100644 --- a/crates/engine/src/tidy_tests.rs +++ b/crates/engine/src/tidy_tests.rs @@ -339,6 +339,19 @@ fn a_negative_gap_is_raised_so_no_layer_reaches_the_one_two_before() { } } +#[test] +fn a_narrow_first_layer_still_keeps_the_second_after_it() { + // Not measured in Figma (it kept the order in every run): the modal gap -40 would start the + // second layer at -30, before a 10 wide first layer, so the gap is raised to -9. + let mut s = Session::new(); + let xs = [0., 80., 200., 260., 400., 460.]; + let widths = [10., 100., 100., 100., 100., 100.]; + let pairs: Vec<(f64, f64)> = xs.iter().copied().zip(widths).collect(); + let ids = bars(&mut s, &pairs); + tidy(&mut s, &ids); + assert_eq!(xs_of(&s, &ids), vec![0., 1., 92., 183., 274., 365.]); +} + #[test] fn a_negative_gap_between_equal_widths_is_the_most_common_gap() { let mut s = Session::new();