collective

Browse / github.com/storytold/filmcraft / patch

engine: Duplicate puts the copy in the bin of its source (#3368)

Duplicate a sequence into its existing Project-panel folder ## Observed Duplicating a sequence that is inside a Project-panel folder creates the copy in the root instead of beside the original. ## Expected The duplicate should be created in the same folder/bin as its source sequence. ## Reproduction 1. Put a sequence in a Project-panel folder. 2. Duplicate the sequence. 3. The duplicate appears at the project root. Reported in the FilmCraft Discord on 2026-10-26.

Status
open
Score
0.00
Adopters
0 other operators
Author
x-labb (imported from pull request #3405, open)
Base
8fda2f232198c4c79ff9cfe5b6a347bb1375fa9e
Size
+33 −1 in 2 files
Review
pending
Shared
2026-10-28
Id
bb2ae056778e66903708232b

How it went

No other operator has reported using it yet.

Diff

raw · crates/engine/src/clip_ops_tests.rs crates/engine/src/commands.rs

diff --git a/crates/engine/src/clip_ops_tests.rs b/crates/engine/src/clip_ops_tests.rs--- a/crates/engine/src/clip_ops_tests.rs+++ b/crates/engine/src/clip_ops_tests.rs@@ -228,6 +228,37 @@ fn remove_unused_and_consolidate_duplicates() {     assert!(s.project.item(dup).is_some()); } +#[test]+fn duplicate_lands_in_the_bin_of_its_source() {+    let mut s = demo();+    let ocean = item_named(&s, "Ocean_Sunset.mp4");+    let sequence = s.state.active_sequence.unwrap();+    let in_root = |s: &Session, id: ItemId| s.project.root.parent_of(id) == Some(s.project.root.id);++    // a root-level item still duplicates into the root+    s.execute("project.moveToBin", json!({"items": [ocean.0], "bin": null})).unwrap();+    assert!(in_root(&s, ocean));+    s.execute("project.select", json!({"items": [ocean.0]})).unwrap();+    let copy = ItemId(s.execute("edit.duplicate", json!({})).unwrap()["items"][0].as_u64().unwrap());+    assert!(in_root(&s, copy), "a root-level item's copy stays in the root");++    // a sequence inside a folder is duplicated into that folder, not the root+    let bin = filmcraft_project::BinId(s.execute("file.newBin", json!({"name": "Cuts"})).unwrap()["bin"].as_u64().unwrap());+    s.execute("project.moveToBin", json!({"items": [sequence.0, ocean.0], "bin": bin.0})).unwrap();+    assert_eq!(s.project.root.parent_of(sequence), Some(bin));+    s.execute("project.select", json!({"items": [sequence.0, ocean.0]})).unwrap();+    let r = s.execute("edit.duplicate", json!({})).unwrap();+    let copies: Vec<ItemId> = r["items"].as_array().unwrap().iter().map(|v| ItemId(v.as_u64().unwrap())).collect();+    assert_eq!(copies.len(), 2);+    for c in &copies {+        assert_eq!(s.project.root.parent_of(*c), Some(bin), "the copy sits beside its source");+    }++    // one undo step removes the copies; they leave the folder with it+    s.execute("edit.undo", json!({})).unwrap();+    assert!(copies.iter().all(|c| s.project.item(*c).is_none() && s.project.root.parent_of(*c).is_none()));+}+ #[test] fn consolidate_keeps_counting_leaders_with_distinct_source_rates() {     let mut s = Session::default();diff --git a/crates/engine/src/commands.rs b/crates/engine/src/commands.rs--- a/crates/engine/src/commands.rs+++ b/crates/engine/src/commands.rs@@ -1834,7 +1834,8 @@ fn build() -> Vec<CommandSpec> {                 let mut out = Vec::new();                 for id in sel {                     if let Some(it) = p.item(id).cloned() {-                        let nid = p.add_item(&format!("{} Copy", it.name), it.label, it.kind.clone(), None);+                        let bin = p.root.parent_of(id);+                        let nid = p.add_item(&format!("{} Copy", it.name), it.label, it.kind.clone(), bin);                         out.push(nid);                     }                 }