From 2b7733585478a48309564520b7e4890308d7ee27 Mon Sep 17 00:00:00 2001 From: cocvn157 <120394358+cocvn157@users.noreply.github.com> Date: Sat, 7 Nov 2026 11:54:23 +0700 Subject: [PATCH 1/2] fix(editor): refresh project snapshot on watch receiver updates to invalidate legacy zoom after split (#2230) --- crates/editor/src/editor_instance.rs | 80 +++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 7 deletions(-) diff --git a/crates/editor/src/editor_instance.rs b/crates/editor/src/editor_instance.rs index fbdd653a97b..2c32edf34f7 100644 --- a/crates/editor/src/editor_instance.rs +++ b/crates/editor/src/editor_instance.rs @@ -181,13 +181,11 @@ fn refresh_project_snapshot( project_rx: &mut watch::Receiver, snapshot: &mut Arc, ) -> Option> { - let refreshed = { - let project = project_rx.borrow_and_update(); - if !project.has_changed() { - return None; - } - Arc::new(project.clone()) - }; + if !project_rx.has_changed().unwrap_or(false) { + return None; + } + let project = project_rx.borrow_and_update(); + let refreshed = Arc::new(project.clone()); Some(std::mem::replace(snapshot, refreshed)) } @@ -2493,6 +2491,74 @@ mod tests { assert!(Arc::ptr_eq(&snapshot, &refreshed)); } + #[test] + fn preview_project_snapshot_detects_zoom_split_and_area_adjustment() { + use cap_project::{ZoomMode, ZoomSegment}; + + let initial_zoom = ZoomSegment { + start: 1.0, + end: 5.0, + amount: 2.0, + mode: ZoomMode::Manual { x: 0.25, y: 0.25 }, + glide_direction: Default::default(), + glide_speed: 0.5, + instant_animation: false, + edge_snap_ratio: 0.25, + }; + let mut initial_config = ProjectConfiguration::default(); + initial_config.timeline = Some(TimelineConfiguration { + zoom_segments: vec![initial_zoom], + ..Default::default() + }); + + let (project_tx, mut project_rx) = watch::channel(initial_config); + let mut snapshot = Arc::new(project_rx.borrow_and_update().clone()); + let initial = Arc::clone(&snapshot); + + // User splits zoom into two, then adjusts the new split zoom to a new area (x: 0.75, y: 0.75) + let split_head = ZoomSegment { + start: 1.0, + end: 3.0, + amount: 2.0, + mode: ZoomMode::Manual { x: 0.25, y: 0.25 }, + glide_direction: Default::default(), + glide_speed: 0.5, + instant_animation: false, + edge_snap_ratio: 0.25, + }; + let split_tail = ZoomSegment { + start: 3.0, + end: 5.0, + amount: 2.0, + mode: ZoomMode::Manual { x: 0.75, y: 0.75 }, + glide_direction: Default::default(), + glide_speed: 0.5, + instant_animation: false, + edge_snap_ratio: 0.25, + }; + + project_tx.send_modify(|project| { + if let Some(timeline) = project.timeline.as_mut() { + timeline.zoom_segments = vec![split_head, split_tail]; + } + }); + + let previous = refresh_project_snapshot(&mut project_rx, &mut snapshot).unwrap(); + assert!(Arc::ptr_eq(&previous, &initial)); + assert!(!Arc::ptr_eq(&snapshot, &initial)); + + let screen_size = XY::new(1920, 1080); + assert!(!ZoomTransformTimeline::project_inputs_match( + &previous, + &snapshot, + screen_size + )); + + let next_zooms = &snapshot.timeline.as_ref().unwrap().zoom_segments; + assert_eq!(next_zooms.len(), 2); + assert_eq!(next_zooms[1].mode, ZoomMode::Manual { x: 0.75, y: 0.75 }); + } + #[test] fn audio_timing_repair_uses_startup_trimmed_overlap() { let summary = AudioGapSummary { From 0433190d1b9f07beb571f96ae8786770777fb6f4 Mon Sep 17 00:00:00 2001 From: cocvn157 <120394358+cocvn157@users.noreply.github.com> Date: Sun, 8 Nov 2026 08:21:13 +0700 Subject: [PATCH 2/2] test(editor): construct TimelineConfiguration explicitly and remove setup narration --- crates/editor/src/editor_instance.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor_instance.rs b/crates/editor/src/editor_instance.rs index 2c32edf34f7..0a28a99bf46 100644 --- a/crates/editor/src/editor_instance.rs +++ b/crates/editor/src/editor_instance.rs @@ -2507,15 +2507,25 @@ mod tests { }; let mut initial_config = ProjectConfiguration::default(); initial_config.timeline = Some(TimelineConfiguration { + segments: Vec::new(), + transitions: Vec::new(), zoom_segments: vec![initial_zoom], - ..Default::default() + scene_segments: Vec::new(), + mask_segments: Vec::new(), + text_segments: Vec::new(), + caption_segments: Vec::new(), + keyboard_segments: Vec::new(), + audio_segments: Vec::new(), + style_segments: Vec::new(), + image_segments: Vec::new(), + shape_segments: Vec::new(), + camera3d_segments: Vec::new(), }); let (project_tx, mut project_rx) = watch::channel(initial_config); let mut snapshot = Arc::new(project_rx.borrow_and_update().clone()); let initial = Arc::clone(&snapshot); - // User splits zoom into two, then adjusts the new split zoom to a new area (x: 0.75, y: 0.75) let split_head = ZoomSegment { start: 1.0, end: 3.0,