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
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 = ?")