Restructure move_task as a single match on bounds
Also isolate coverage builds in target/coverage/ to prevent
stale debug info from non-instrumented builds causing phantom
uncovered lines.

Assisted-by: Claude Opus 4.6 via pi
change nkskxmyzqznppwztuzylnpknvpqosnzv
commit 417dc5bcfc51ff0deb50b88001f2f12cb93b2ac4
author Alpha Chen <alpha@kejadlen.dev>
date
parent turtykyl
diff --git a/.agents/skills/rust-coverage/SKILL.md b/.agents/skills/rust-coverage/SKILL.md
index 586799f..e27e479 100644
--- a/.agents/skills/rust-coverage/SKILL.md
+++ b/.agents/skills/rust-coverage/SKILL.md
@@ -236,7 +236,7 @@ llvm-cov show -object bin1 -object bin2 --instr-profile=merged.profdata
 
 **Dead code appears uncovered**: Unreachable code still gets instrumented. If coverage matters, delete dead code rather than excluding it.
 
-**Stale incremental artifacts with grcov**: Incremental compilation reuses old object files with outdated debug info, causing grcov to report phantom uncovered lines. Set `CARGO_INCREMENTAL=0` for coverage builds, or run `cargo clean` before measuring.
+**Stale incremental artifacts with grcov**: Incremental compilation reuses old object files with outdated debug info, causing grcov to report phantom uncovered lines. Use a separate `CARGO_TARGET_DIR` for coverage builds so instrumented and non-instrumented artifacts never mix.
 
 **Proc macros and build scripts**: Not instrumented by default. Use `--include-build-script` if needed. When using `--no-rustc-wrapper` with `--target`, proc macros won't show coverage.
 
@@ -246,11 +246,13 @@ llvm-cov show -object bin1 -object bin2 --instr-profile=merged.profdata
 
 ## This Project
 
-This project uses `just coverage` which runs grcov with covdir output:
+This project uses `just coverage` which builds into an isolated `target/coverage/` directory and runs grcov with covdir output:
 
 ```bash
-RUSTFLAGS="-Cinstrument-coverage" cargo test --workspace
-grcov target/coverage --binary-path ./target/debug/ -s . -t covdir \
+RUSTFLAGS="-Cinstrument-coverage" CARGO_TARGET_DIR=target/coverage \
+    cargo test --workspace
+grcov target/coverage/profraw \
+    --binary-path ./target/coverage/debug/ -s . -t covdir \
     --keep-only 'src/**' --ignore 'src/bin/**' \
     --excl-line 'cov-excl-line' --excl-start 'cov-excl-start' --excl-stop 'cov-excl-stop'
 ```
@@ -260,7 +262,8 @@ grcov target/coverage --binary-path ./target/debug/ -s . -t covdir \
 To find uncovered lines, use the markdown output:
 
 ```bash
-grcov target/coverage --binary-path ./target/debug/ -s . -t markdown \
+grcov target/coverage/profraw \
+    --binary-path ./target/coverage/debug/ -s . -t markdown \
     --keep-only 'src/**' --ignore 'src/bin/**' \
     --excl-line 'cov-excl-line' --excl-start 'cov-excl-start' --excl-stop 'cov-excl-stop'
 ```
diff --git a/justfile b/justfile
index 9e39c32..acf3d64 100644
--- a/justfile
+++ b/justfile
@@ -18,12 +18,12 @@ coverage:
     #!/usr/bin/env bash
     set -euo pipefail
     export RUSTFLAGS="-Cinstrument-coverage"
-    export CARGO_INCREMENTAL=0
-    export LLVM_PROFILE_FILE="target/coverage/%p-%m.profraw"
-    rm -rf target/coverage
+    export CARGO_TARGET_DIR="target/coverage"
+    export LLVM_PROFILE_FILE="target/coverage/profraw/%p-%m.profraw"
+    rm -rf target/coverage/profraw
     cargo test --workspace -q
-    REPORT=$(grcov target/coverage \
-        --binary-path ./target/debug/ \
+    REPORT=$(grcov target/coverage/profraw \
+        --binary-path ./target/coverage/debug/ \
         -s . \
         -t covdir \
         --ignore-not-existing \
diff --git a/src/ops/task.rs b/src/ops/task.rs
index 05293e4..448b4f4 100644
--- a/src/ops/task.rs
+++ b/src/ops/task.rs
@@ -164,83 +164,75 @@ pub async fn move_task(
     before_task_id: Option<i64>,
     after_task_id: Option<i64>,
 ) -> Result<(), RangerError> {
-    let new_pos = if before_task_id.is_none() && after_task_id.is_none() {
-        // Append to end of backlog
-        let last_pos: Option<String> = sqlx::query_scalar(
-            "SELECT bt.position FROM backlog_tasks bt \
-             WHERE bt.backlog_id = ? \
-             ORDER BY bt.position DESC LIMIT 1",
+    // "before" task = the task we want to appear after us (upper bound)
+    // "after" task = the task we want to appear before us (lower bound)
+    let upper: Option<String> = if let Some(id) = before_task_id {
+        sqlx::query_scalar(
+            "SELECT position FROM backlog_tasks WHERE backlog_id = ? AND task_id = ?",
         )
         .bind(backlog_id)
+        .bind(id)
         .fetch_optional(&mut *conn)
-        .await?;
+        .await?
+    } else {
+        None
+    };
 
-        position::between(last_pos.as_deref().unwrap_or(""), "")
+    let lower: Option<String> = if let Some(id) = after_task_id {
+        sqlx::query_scalar(
+            "SELECT position FROM backlog_tasks WHERE backlog_id = ? AND task_id = ?",
+        )
+        .bind(backlog_id)
+        .bind(id)
+        .fetch_optional(&mut *conn)
+        .await?
     } else {
-        // "before" task = the task we want to appear after us (upper bound)
-        // "after" task = the task we want to appear before us (lower bound)
-        let upper_bound: Option<String> = if let Some(id) = before_task_id {
-            sqlx::query_scalar(
-                "SELECT position FROM backlog_tasks WHERE backlog_id = ? AND task_id = ?",
+        None
+    };
+
+    let new_pos = match (lower.as_deref(), upper.as_deref()) {
+        // No bounds: append to end of backlog
+        (None, None) => {
+            let last_pos: Option<String> = sqlx::query_scalar(
+                "SELECT bt.position FROM backlog_tasks bt \
+                 WHERE bt.backlog_id = ? \
+                 ORDER BY bt.position DESC LIMIT 1",
             )
             .bind(backlog_id)
-            .bind(id)
             .fetch_optional(&mut *conn)
-            .await?
-        } else {
-            None
-        };
-
-        let lower_bound: Option<String> = if let Some(id) = after_task_id {
-            sqlx::query_scalar(
-                "SELECT position FROM backlog_tasks WHERE backlog_id = ? AND task_id = ?",
+            .await?;
+            position::between(last_pos.as_deref().unwrap_or(""), "")
+        }
+        // After only: find the next task as upper bound
+        (Some(low), None) => {
+            let next: Option<String> = sqlx::query_scalar(
+                "SELECT position FROM backlog_tasks \
+                 WHERE backlog_id = ? AND task_id != ? AND position > ? \
+                 ORDER BY position ASC LIMIT 1",
             )
             .bind(backlog_id)
-            .bind(id)
+            .bind(task_id)
+            .bind(low)
             .fetch_optional(&mut *conn)
-            .await?
-        } else {
-            None
-        };
-
-        // When only one bound is given, find the adjacent task's position
-        // to avoid collisions with existing positions.
-        let (lower, upper) = match (&lower_bound, &upper_bound) {
-            (Some(low), None) => {
-                // --after only: find the next task's position as upper bound
-                let next: Option<String> = sqlx::query_scalar(
-                    "SELECT position FROM backlog_tasks \
-                     WHERE backlog_id = ? AND task_id != ? AND position > ? \
-                     ORDER BY position ASC LIMIT 1",
-                )
-                .bind(backlog_id)
-                .bind(task_id)
-                .bind(low)
-                .fetch_optional(&mut *conn)
-                .await?;
-                (Some(low.clone()), next)
-            }
-            (None, Some(up)) => {
-                // --before only: find the previous task's position as lower bound
-                let prev: Option<String> = sqlx::query_scalar(
-                    "SELECT position FROM backlog_tasks \
-                     WHERE backlog_id = ? AND task_id != ? AND position < ? \
-                     ORDER BY position DESC LIMIT 1",
-                )
-                .bind(backlog_id)
-                .bind(task_id)
-                .bind(up)
-                .fetch_optional(&mut *conn)
-                .await?;
-                (prev, Some(up.clone()))
-            }
-            _ => (lower_bound.clone(), upper_bound.clone()),
-        };
-
-        position::between(
-            lower.as_deref().unwrap_or(""),
-            upper.as_deref().unwrap_or(""),
-        )
+            .await?;
+            position::between(low, next.as_deref().unwrap_or(""))
+        }
+        // Before only: find the previous task as lower bound
+        (None, Some(up)) => {
+            let prev: Option<String> = sqlx::query_scalar(
+                "SELECT position FROM backlog_tasks \
+                 WHERE backlog_id = ? AND task_id != ? AND position < ? \
+                 ORDER BY position DESC LIMIT 1",
+            )
+            .bind(backlog_id)
+            .bind(task_id)
+            .bind(up)
+            .fetch_optional(&mut *conn)
+            .await?;
+            position::between(prev.as_deref().unwrap_or(""), up)
+        }
+        // Both bounds given
+        (Some(low), Some(up)) => position::between(low, up),
     };
 
     sqlx::query("UPDATE backlog_tasks SET position = ? WHERE backlog_id = ? AND task_id = ?")