Work area: an edit never clamps with crossed bounds (#605 follow-up)

A project saved with an inverted work area, or a comp shorter than a
frame, made Tick::clamp panic on the next start or end edit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
This commit is contained in:
Claude committed 2026-10-10 16:28:54 +00:00
1 parent 359ad52f99
commit bb08a6acc9
2 files changed
+17 -2

No files matched your search

+3 -2
View File
@@ -182,10 +182,11 @@ fn work_area(s: &mut Session, p: &Value) -> Result<Value> {
_ => {}
}
if let Some(b) = begin {
c.work_area.0 = b.clamp(Tick::ZERO, c.duration - fd);
// min/max, not clamp: the bounds cross on a comp shorter than a frame or an inverted area.
c.work_area.0 = b.min(c.duration - fd).max(Tick::ZERO);
}
if let Some(e) = end {
c.work_area.1 = e.clamp(c.work_area.0 + fd, c.duration);
c.work_area.1 = e.max(c.work_area.0 + fd).min(c.duration);
}
// A start on its own can land after the current end. Pull it back so the interval
// still covers one frame, the same rule as `set: "begin"`.
+14
View File
@@ -196,6 +196,7 @@ fn separated_position_follows_canvas_resize_and_crop() {
placed(&separated, b, 1.0, 50.0, 30.0);
assert_eq!(opaque_bounds(&separated, Tick::ZERO), (70, 70, 25, 25, 34, 34, 100));
assert_eq!(opaque_bounds(&separated, Tick::from_seconds_f64(1.0)), (70, 70, 45, 25, 54, 34, 100));
}
/// Pixel-centre of the opaque samples, so a 10×10 solid centred on (11, 22) reports (11, 22).
fn opaque_centroid(s: &Session) -> (f64, f64, u32) {
@@ -399,6 +400,19 @@ fn lift_and_extract_work_area() {
assert!((spans[1].0 - 2.0).abs() < 1e-6 && (spans[1].1 - 8.0).abs() < 1e-6, "{spans:?}");
}
/// A project saved with an inverted work area (by an older build) is edited without a panic:
/// `clamp` with crossed bounds would abort.
#[test]
fn an_inverted_saved_work_area_takes_an_end_edit() {
let mut s = comp();
let cid = s.active_comp_id().unwrap();
std::sync::Arc::make_mut(&mut s.project).comp_mut(cid).unwrap().work_area = (Tick::from_seconds_f64(9.0), Tick::from_seconds_f64(2.0));
s.execute("comp.workArea", json!({"end": 1.0})).unwrap();
s.execute("comp.workArea", json!({"start": 50.0})).unwrap();
let c = s.active_comp().unwrap();
assert!(c.work_area.0 >= Tick::ZERO && c.work_area.0 + c.frame_duration() <= c.work_area.1 && c.work_area.1 <= c.duration);
}
#[test]
fn work_area_stays_ordered_when_the_start_passes_the_end() {
let ordered = |s: &Session| {