Add --before/--after position flags to task edit
Lets you reposition a task in the same command that changes its
state or title, avoiding a separate `task move` call.

Assisted-by: Claude Opus 4.6 via pi
change qtxwsnsppktrnusssyoxkkxowxynkrus
commit 6ace1778212affa1470cc3f20fb3f630bd09fcfa
author Alpha Chen <alpha@kejadlen.dev>
date
parent oxrpompw
diff --git a/src/bin/ranger/commands/task.rs b/src/bin/ranger/commands/task.rs
index e03b4bc..7c37a50 100644
--- a/src/bin/ranger/commands/task.rs
+++ b/src/bin/ranger/commands/task.rs
@@ -88,6 +88,8 @@ pub enum TaskCommands {
         /// New state
         #[arg(long)]
         state: Option<String>,
+        #[command(flatten)]
+        position: PositionArgs,
     },
     /// Move a task's position within its backlog
     #[command(visible_alias = "mv")]
@@ -222,11 +224,14 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result
             title,
             description,
             state,
+            position,
         } => {
             let mut conn = pool.acquire().await?;
             let state = state.map(|s| s.parse::<State>()).transpose()?;
 
             let task = ops::task::get_by_key_prefix(&mut conn, &key).await?;
+            let (before_id, after_id) = position.resolve(&mut conn).await?;
+
             let updated = ops::task::edit(
                 &mut conn,
                 task.id,
@@ -235,6 +240,11 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result
                 state,
             )
             .await?;
+
+            if before_id.is_some() || after_id.is_some() {
+                ops::task::move_task(&mut conn, updated.id, before_id, after_id).await?;
+            }
+
             output::print(&updated, json, print_task);
         }
         TaskCommands::Move { key, position } => {
diff --git a/tests/cli.rs b/tests/cli.rs
index 500d385..491ea14 100644
--- a/tests/cli.rs
+++ b/tests/cli.rs
@@ -110,6 +110,68 @@ fn full_workflow() {
     assert_eq!(detail["tags"][0]["name"], "urgent");
     assert_eq!(detail["blockers"].as_array().unwrap().len(), 1);
 
+    // Create a third task and use edit --before to reposition it
+    let output = ranger(db_path)
+        .args(["task", "create", "Third task", "--state", "queued"])
+        .output()
+        .unwrap();
+    assert!(output.status.success());
+
+    let output = ranger(db_path)
+        .args(["task", "list", "--json", "--state", "queued"])
+        .output()
+        .unwrap();
+    let tasks_before_move: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
+    let tasks_before_move = tasks_before_move.as_array().unwrap();
+    // Third task should be after Second task (both queued, but Second was icebox — actually
+    // let's just get the keys and verify edit --before works)
+    let t3_key = tasks_before_move
+        .iter()
+        .find(|t| t["title"] == "Third task")
+        .unwrap()["key"]
+        .as_str()
+        .unwrap()
+        .to_string();
+
+    // Edit Third task: change title AND reposition before First task
+    let output = ranger(db_path)
+        .args([
+            "task",
+            "edit",
+            &t3_key[..4],
+            "--title",
+            "Third task (edited)",
+            "--before",
+            &t1_key[..4],
+        ])
+        .output()
+        .unwrap();
+    assert!(output.status.success());
+    let stdout = String::from_utf8(output.stdout).unwrap();
+    assert!(stdout.contains("Third task (edited)"));
+
+    // Verify ordering: Third should now be before First
+    let output = ranger(db_path)
+        .args(["task", "list", "--json"])
+        .output()
+        .unwrap();
+    let tasks_after_move: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
+    let tasks_after_move = tasks_after_move.as_array().unwrap();
+    let titles: Vec<&str> = tasks_after_move
+        .iter()
+        .map(|t| t["title"].as_str().unwrap())
+        .collect();
+    let third_pos = titles
+        .iter()
+        .position(|t| *t == "Third task (edited)")
+        .unwrap();
+    let first_pos = titles.iter().position(|t| *t == "First task").unwrap();
+    assert!(
+        third_pos < first_pos,
+        "Third task should be before First task after edit --before, got: {:?}",
+        titles
+    );
+
     // Delete a task
     let output = ranger(db_path)
         .args(["task", "delete", &t2_key[..4]])
@@ -124,5 +186,5 @@ fn full_workflow() {
         .unwrap();
     assert!(output.status.success());
     let tasks: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
-    assert_eq!(tasks.as_array().unwrap().len(), 1);
+    assert_eq!(tasks.as_array().unwrap().len(), 2);
 }