From 8a98451d2d564fbe938c9bc411d707b8efbcbb4a Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Wed, 14 Oct 2026 22:49:37 -0400 Subject: [PATCH] fix(flex): flex.reorder clamps a huge `by` instead of overflowing (#973) Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/cmd/flex.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/cmd/flex.rs b/crates/engine/src/cmd/flex.rs index bdb7f73e..e8ef451a 100644 --- a/crates/engine/src/cmd/flex.rs +++ b/crates/engine/src/cmd/flex.rs @@ -378,7 +378,7 @@ fn reorder(s: &mut Session, p: &Value) -> Result { let Content::Group { items } = &mut c.content else { return Err(bad("flex.reorder", "the object isn't in a Flex Layout")) }; let n = items.len() as i64; let from = *last as i64; - let target = to.unwrap_or(from + by.unwrap_or(0)).clamp(0, n - 1); + let target = to.unwrap_or(from.saturating_add(by.unwrap_or(0))).clamp(0, n - 1); let moved = items.remove(from as usize); items.insert(target as usize, moved); *sel = Selection::items(vec![id]); @@ -428,6 +428,16 @@ mod tests { s.doc().unwrap().doc.item(ItemId(id)).unwrap().bounds() } + #[test] + fn reorder_by_a_huge_step_clamps_instead_of_overflowing() { + let (mut s, ids) = session_with_boxes(&[[84.0, 84.0, 184.0, 184.0], [284.0, 84.0, 384.0, 184.0]]); + s.execute("selection.set", &json!({"ids": ids})).unwrap(); + s.execute("flex.add", &json!({})).unwrap(); + // `from + by` used to overflow (a panic with overflow checks on). + s.execute("flex.reorder", &json!({"id": ids[0], "by": i64::MAX})).unwrap(); + s.execute("flex.reorder", &json!({"id": ids[0], "by": i64::MIN})).unwrap(); + } + /// The video: four objects in a row become a container that keeps them where they are (Row, /// Auto size, Space Between at their spacing, aligned to the top), then a fixed 2-column layout. #[test]