Skip to content

feat(download): queue drag-reorder + priority rescheduling (task 12)#105

Merged
mpiton merged 5 commits intomainfrom
feat/task-12-queue-reorder
Apr 25, 2026
Merged

feat(download): queue drag-reorder + priority rescheduling (task 12)#105
mpiton merged 5 commits intomainfrom
feat/task-12-queue-reorder

Conversation

@mpiton
Copy link
Copy Markdown
Owner

@mpiton mpiton commented Apr 25, 2026

Summary

• Implement drag-and-drop queue reordering with dnd-kit and persistent queue_position column
• Add move-to-top/bottom commands with automatic position assignment
• Subscribe QueueManager to priority+reorder events for immediate rescheduling
• Extend sorting to three-level (priority → queue_position → created_at)

Changes

  • Backend: New move_to_top, move_to_bottom, reorder_queue commands; SQLite migration; event publishing
  • Database: queue_position BIGINT column with default sort queue_position ASC → created_at DESC
  • Frontend: Drag-handle rendering, SortableRow wrapper, computeReorderedIds() helper, move actions in context menu
  • Tests: 13 new backend tests (5 move_queue + 2 queue_manager + 1 SQLite), 6 new Vitest tests (dnd handlers)

Test Coverage

  • Rust: 782 tests passing (move_queue, queue_manager ordering, set_priority events)
  • TypeScript: 546 tests passing (drag behavior, IPC mutations)
  • All acceptance criteria verified (persistence, rescheduling, sorting)

Summary by cubic

Adds drag-and-drop queue reordering with persistent positions and instant rescheduling when priority or order changes. New downloads now append to the back of the manual queue, with a global queue lock to prevent race conditions. Completes PRD‑v2 P0.12 (task 12).

  • New Features

    • Drag-and-drop reorder in Downloads using @dnd-kit/core + @dnd-kit/sortable + @dnd-kit/utilities; drag handle on Queued/Retry/Waiting rows; menu adds Move to top/Move to bottom.
    • New IPC: download_move_to_top, download_move_to_bottom, download_reorder_queue; emits DownloadPrioritySet and QueueReordered.
    • Persistent order via queue_position; default list sort is queue ASC → created_at DESC; queue/queuePosition sort fields supported.
    • Scheduler sorts by priority DESC → queue_position ASC → created_at ASC and reschedules on priority/order events.
    • New downloads (download_start, download_redownload) get a back‑of‑queue position, so they don’t jump ahead of manually ordered items.
    • Validation + durability: reject duplicates, non‑reorderable states, and missing IDs; drag uses visually sorted rows; reorder merges omitted items and preserves their original order; writes persist via an atomic DB transaction; a global queue‑position lock prevents concurrent allocation collisions; deterministic SQL ordering in queue reads (ORDER BY queue_position ASC, created_at ASC, id ASC) ensures stable tie‑breakers across processes.
  • Migration

    • Adds queue_position BIGINT NOT NULL DEFAULT 0 with an index (migration m20260425_000004_add_queue_position). Run DB migrations before using queue reorder.

Written for commit d91a2e9. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Drag-and-drop queue reordering with persisted ordering, plus Move-to-top/Move-to-bottom actions
    • Redownload for completed items with overwrite/rename flow
    • Global Link Grabber clipboard paste shortcut and a Settings shortcuts tab; clipboard monitoring toggle
    • OS open-file/folder actions and folder/file pickers in Settings
  • Improvements

    • Scheduler now respects queue positions when tie-breaking by priority
    • Checksum integrity validation; refreshed Statistics and History views
  • i18n

    • Added corresponding EN/FR translations

Implements PRD-v2 P0.12:
- Commands `download_move_to_top`, `download_move_to_bottom`,
  `download_reorder_queue` + `queue_position` column persisted in SQLite
  (migration `m20260425_000004_add_queue_position`).
- `QueueManager` sort now uses priority desc → queue_position asc →
  created_at asc, and subscribes to new `DownloadPrioritySet` /
  `QueueReordered` events so set_priority triggers immediate
  rescheduling — a high-priority item starts as soon as a slot is free.
- Default `download_list` sort is `queue_position ASC → created_at DESC`
  so fresh downloads (position 0) stay newest-first while manually
  reordered rows stick across reloads.
- Frontend wires `@dnd-kit/core` + `@dnd-kit/sortable` into
  `DownloadsTable` with a drag handle column (only enabled for
  Queued/Retry/Waiting rows) and Move to top / Move to bottom menu
  items. `computeReorderedIds` helper filters non-reorderable IDs from
  the new order before invoking `download_reorder_queue`.
- Covered by new unit tests in the queue_manager, move_queue, and
  set_priority modules (Rust) and new Vitest cases in
  DownloadsTable.test.tsx (drag handler + move-to-top/bottom IPC).
@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies configuration rust frontend labels Apr 25, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds persistent per-download queue_position, new domain events (DownloadPrioritySet, QueueReordered), DB migration and repo support (including save_batch), command handlers for move/reorder and position allocation locking, QueueManager scheduling changes, Tauri IPC + event bridge updates, frontend DnD-based queue reordering with DTO/i18n updates and tests.

Changes

Cohort / File(s) Summary
Database & Persistence
src-tauri/src/adapters/driven/sqlite/migrations/m20260425_000004_add_queue_position.rs, src-tauri/src/adapters/driven/sqlite/entities/download.rs, src-tauri/src/adapters/driven/sqlite/migrations/mod.rs, src-tauri/src/adapters/driven/sqlite/download_repo.rs, src-tauri/src/adapters/driven/sqlite/download_read_repo.rs, src-tauri/src/adapters/driven/sqlite/progress_bridge.rs
Add non-null queue_position column + index; map field entity↔domain; extend upsert/read to persist/query by queue_position; add save_batch; update tests/fixtures.
Domain Model & Events
src-tauri/src/domain/model/download.rs, src-tauri/src/domain/model/views.rs, src-tauri/src/domain/event.rs
Download gains queue_position with builder/mutator/getter; DownloadView adds priority and queue_position; SortField adds QueuePosition; add DownloadPrioritySet and QueueReordered DomainEvent variants.
Application Commands & Queue Logic
src-tauri/src/application/commands/mod.rs, src-tauri/src/application/commands/move_queue.rs, src-tauri/src/application/commands/set_priority.rs, src-tauri/src/application/commands/redownload.rs, src-tauri/src/application/commands/start_download.rs, src-tauri/src/application/services/queue_manager.rs
Add MoveToTop, MoveToBottom, ReorderQueue commands and handlers (validation, stride/saturation math, renumbering, transactional save_batch, publish QueueReordered); set_priority publishes DownloadPrioritySet; starting/redownloading assign queue positions under lock; QueueManager tie-breaks use queue_position and listens to new events.
CommandBus & Concurrency
src-tauri/src/application/command_bus.rs, src-tauri/src/domain/ports/driven/download_repository.rs
Add queue_position_lock mutex and lock_queue_positions() accessor; add DownloadRepository::save_batch trait method with default implementation.
IPC, Event Bridging & Logging
src-tauri/src/adapters/driving/tauri_ipc.rs, src-tauri/src/adapters/driven/event/tauri_bridge.rs, src-tauri/src/adapters/driven/logging/download_log_bridge.rs, src-tauri/src/lib.rs
Expose Tauri commands (download_move_to_top, download_move_to_bottom, download_reorder_queue); bridge emits download-priority-set and queue-reordered; logging bridge ignores these events; IPC registration/exports updated.
Frontend Types & DTOs
src/types/download.ts, src-tauri/src/application/read_models/download_view.rs, src-tauri/src/application/queries/get_downloads.rs
Frontend types add queue/queuePosition sort options and priority/queuePosition fields; DTO mapping and tests updated.
Downloads Table UI & Tests
src/views/DownloadsView/DownloadsTable.tsx, src/views/DownloadsView/__tests__/DownloadsTable.test.tsx, src/views/DownloadsView/__tests__/DownloadsView.test.tsx, package.json
Integrate @dnd-kit for drag-and-drop reordering; add isReorderable and computeReorderedIds; drag handle/SortableRow; move-to-top/bottom actions wired to IPC; drag end persists via download_reorder_queue; tests and fixtures updated; DnD deps added.
I18n & Changelog
src/i18n/locales/en.json, src/i18n/locales/fr.json, CHANGELOG.md
Add i18n keys for move-to-top/move-to-bottom and document unreleased queue-reorder features.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Frontend as Frontend (DownloadsTable)
    participant Tauri as Tauri IPC
    participant CmdBus as CommandBus
    participant Repo as DownloadRepo
    participant DB as SQLite
    participant EventBus as EventBus
    participant QueueMgr as QueueManager

    User->>Frontend: Drag item or click move action
    Frontend->>Frontend: computeReorderedIds() / prepare orderedIds
    Frontend->>Tauri: download_reorder_queue(orderedIds) or download_move_to_top/bottom
    Tauri->>CmdBus: ReorderQueueCommand / MoveToTopCommand
    CmdBus->>CmdBus: lock_queue_positions()
    CmdBus->>Repo: load reorderable items (SELECT queue_position...)
    Repo->>DB: query
    DB-->>Repo: rows
    CmdBus->>Repo: save_batch(updated downloads)
    Repo->>DB: BEGIN / UPDATE queue_position... / COMMIT
    DB-->>Repo: OK
    CmdBus->>EventBus: publish(QueueReordered / DownloadPrioritySet)
    EventBus-->>QueueMgr: deliver event
    QueueMgr->>QueueMgr: on_slot_freed() -> sort by priority, queue_position, created_at
    EventBus-->>Tauri: bridge emits "queue-reordered"/"download-priority-set"
    Tauri-->>Frontend: frontend receives event and refreshes UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

ui

Poem

🐰 I nudge the list with gentle paws,
Numbers march in tidy rows and laws.
Drag, drop, top or bottom too—
Every download finds its cue.
Hops of joy for queues made true! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.39% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the primary feature: drag-and-drop queue reordering combined with priority-based rescheduling, both central to this substantial changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/task-12-queue-reorder

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 25, 2026

Greptile Summary

This PR completes PRD-v2 P0.12 (task 12) by adding drag-and-drop queue reordering with a persistent queue_position column, move-to-top/bottom IPC commands, and automatic rescheduling on priority or order changes. All three issues flagged in the previous review round (missing state guard on move-to-top/bottom, stride-of-1 position starvation, new downloads landing at position 0 ahead of reordered items) are fully resolved.

Confidence Score: 5/5

Safe to merge; all previously flagged P1s are resolved, and the single remaining finding is a P2 UX refinement.

No P0 or P1 issues found. The only finding is a P2 style/UX concern: sortableIds includes non-reorderable rows, causing unwanted visual transforms on Downloading/Completed rows during drag. Backend logic is solid — state guards, atomic batch transactions, position-lock, and correct queue_position assignment for new/redownloaded items are all in place. 782 Rust tests and 546 TS tests pass.

src/views/DownloadsView/DownloadsTable.tsx — the sortableIds memo should exclude non-reorderable rows to avoid visual transform artefacts and confusing over-target behaviour.

Important Files Changed

Filename Overview
src/views/DownloadsView/DownloadsTable.tsx Adds DndContext/SortableContext/SortableRow for drag-and-drop reordering. Hooks are correctly placed before early returns (previous issue addressed). computeReorderedIds and handleDragEnd are well-structured. Minor issue: sortableIds includes non-reorderable rows, causing unwanted visual transforms and confusing over-target behaviour during drag.
src-tauri/src/application/commands/move_queue.rs New move_to_top, move_to_bottom, and reorder_queue handlers with state guards, POSITION_STRIDE, duplicate-ID rejection, and atomic batch persistence. Previously flagged issues (missing state guard, stride-of-1 starvation) are fully addressed. Well-tested with 10+ unit tests.
src-tauri/src/application/commands/start_download.rs Acquires queue_position_lock before allocating position via next_queue_position(), so new downloads append to the back of the queue rather than landing at position 0 and jumping ahead of reordered items. Previous regression is resolved.
src-tauri/src/application/services/queue_manager.rs QueueManager now subscribes to DownloadPrioritySet and QueueReordered events and calls on_slot_freed() for both. Scheduling sort updated to priority DESC → queue_position ASC → created_at ASC. Two new tests verify the tie-breaking behaviour.
src-tauri/src/adapters/driven/sqlite/download_repo.rs Refactored save logic into persist_download helper; added save_batch with proper SQLite transaction for atomic reordering; find_by_state now returns deterministic order (queue_position ASC, created_at ASC, id ASC); QueuePosition added to upsert update_columns.
src-tauri/src/adapters/driven/sqlite/migrations/m20260425_000004_add_queue_position.rs Clean migration adding queue_position BIGINT NOT NULL DEFAULT 0 with index and reversible down() migration. Existing rows default to 0, preserving created_at DESC secondary sort for un-reordered items.

Sequence Diagram

sequenceDiagram
    participant UI as DownloadsTable (React)
    participant IPC as Tauri IPC
    participant CB as CommandBus
    participant Repo as SqliteDownloadRepo
    participant EB as EventBus
    participant QM as QueueManager

    Note over UI: User drags row / clicks Move to Top
    UI->>UI: computeReorderedIds()
    UI->>IPC: download_reorder_queue / download_move_to_top
    IPC->>CB: handle_reorder_queue / handle_move_to_top
    CB->>CB: lock_queue_positions()
    CB->>Repo: load_reorderable_pool()
    CB->>Repo: save_batch() [atomic transaction]
    CB->>EB: publish(QueueReordered)
    EB-->>QM: QueueReordered event
    QM->>QM: on_slot_freed()
    QM->>Repo: find_by_state(Queued/Retry)
    QM->>QM: sort by priority DESC → queue_position ASC → created_at ASC
    QM->>Repo: save(Downloading)
    QM->>IPC: DownloadStarted event → UI invalidates cache
Loading

Fix All in Claude Code

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/views/DownloadsView/DownloadsTable.tsx
Line: 663-664

Comment:
**Non-reorderable rows included as DnD collision targets and receive visual transforms**

`sortableIds` is built from all rows (`rows.map((r) => r.original.id)`), including Downloading, Completed, Paused, etc. Because these IDs are passed to `SortableContext.items`, dnd-kit will:
1. Use non-reorderable rows as valid `over` targets during a drag — so `event.over?.id` can be a Downloading row's ID, and `computeReorderedIds` silently produces the same order (the dragged reorderable item didn't cross any reorderable boundary), causing the drag to appear to "snap back" with no visible change.
2. Apply CSS transforms to non-reorderable rows (they call `useSortable` and the strategy shifts them to make room), so Downloading/Completed rows will visually slide around while the user is dragging a Queued item — which looks wrong.

Limit `sortableIds` to reorderable rows only:
```suggestion
  const sortableIds = useMemo(
    () => rows.filter((r) => isReorderable(r.original.state)).map((r) => r.original.id),
    [rows],
  );
```
With this fix, non-reorderable rows no longer receive transforms or act as collision targets, and `computeReorderedIds` will get an `overId` that is always a reorderable item's ID.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (5): Last reviewed commit: "fix(download): SQL ORDER BY on find_by_s..." | Re-trigger Greptile

Comment thread src/views/DownloadsView/DownloadsTable.tsx Outdated
Comment thread src-tauri/src/application/commands/move_queue.rs
Comment thread src-tauri/src/application/commands/move_queue.rs
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
src-tauri/src/domain/event.rs (1)

112-124: Prefer domain type over primitive for event priority payload.

Line 117 currently uses u8; consider emitting the validated domain type (Priority) to keep invariants enforced across publishers/subscribers.

♻️ Suggested refactor
+use crate::domain::model::queue::Priority;
 ...
     DownloadPrioritySet {
         id: DownloadId,
-        priority: u8,
+        priority: Priority,
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/domain/event.rs` around lines 112 - 124, The
DownloadPrioritySet event currently carries a primitive u8 for priority which
bypasses domain validation; change its payload to use the domain type Priority
instead (i.e., DownloadPrioritySet { id: DownloadId, priority: Priority }) so
publishers/subscribers preserve invariants, and update all constructors/emitters
and match arms handling DownloadPrioritySet to accept and propagate Priority
rather than u8 (ensure any conversions/validation occur at the boundary where
raw values enter the system).
src-tauri/src/adapters/driven/event/tauri_bridge.rs (1)

60-61: Add explicit bridge-contract tests for the new queue events.

The mapping is good, but it’s worth pinning event_name + payload shape for DownloadPrioritySet and QueueReordered to prevent silent frontend contract regressions.

✅ Suggested test additions
 #[test]
+fn test_event_name_and_payload_queue_management_variants() {
+    let priority_evt = DomainEvent::DownloadPrioritySet {
+        id: DownloadId(11),
+        priority: 9,
+    };
+    let (priority_name, priority_payload) = to_tauri_event(&priority_evt);
+    assert_eq!(priority_name, "download-priority-set");
+    assert_eq!(priority_payload["id"], 11);
+    assert_eq!(priority_payload["priority"], 9);
+
+    let reorder_evt = DomainEvent::QueueReordered {
+        affected_ids: vec![DownloadId(2), DownloadId(5), DownloadId(8)],
+    };
+    let (reorder_name, reorder_payload) = to_tauri_event(&reorder_evt);
+    assert_eq!(reorder_name, "queue-reordered");
+    assert_eq!(reorder_payload["affectedIds"], serde_json::json!([2, 5, 8]));
+}

Also applies to: 139-145

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/adapters/driven/event/tauri_bridge.rs` around lines 60 - 61,
Add explicit bridge-contract tests that assert the emitted event name and
payload shape for the new DomainEvent variants DomainEvent::DownloadPrioritySet
and DomainEvent::QueueReordered as mapped in the event_name logic inside
tauri_bridge.rs; create tests that construct each DomainEvent variant, run it
through the same serialization/mapping path used by the bridge, and assert the
resulting tuple (event_name string and JSON payload keys/types) exactly matches
the expected contract (e.g., "download-priority-set" and "queue-reordered" and
their required payload fields), mirroring the approach used for existing events
so any frontend contract regression fails the test.
src-tauri/src/adapters/driven/sqlite/migrations/m20260425_000004_add_queue_position.rs (1)

24-29: Consider a composite queue-order index for default list sorting.

Given the default ordering is queue-position then creation time, indexing both fields usually performs better than indexing queue_position alone once many rows share the same position (e.g., default 0).

📈 Optional migration adjustment
             .create_index(
                 Index::create()
-                    .name("idx_downloads_queue_position")
+                    .name("idx_downloads_queue_position_created_at")
                     .table(Downloads::Table)
                     .col(Downloads::QueuePosition)
+                    .col(Downloads::CreatedAt)
                     .to_owned(),
             )

 #[derive(DeriveIden)]
 enum Downloads {
     Table,
     QueuePosition,
+    CreatedAt,
 }

Also applies to: 55-59

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src-tauri/src/adapters/driven/sqlite/migrations/m20260425_000004_add_queue_position.rs`
around lines 24 - 29, The created index idx_downloads_queue_position only covers
Downloads::QueuePosition; change it to a composite index on
(Downloads::QueuePosition, Downloads::CreatedAt) so default sorting by queue
position then creation time uses the index — update the Index::create() chain to
add .col(Downloads::CreatedAt) and rename the index (e.g.,
idx_downloads_queue_position_created_at) accordingly; apply the same change to
the other identical index creation block referenced (the one around lines
55-59).
src/views/DownloadsView/__tests__/DownloadsTable.test.tsx (1)

280-283: Minor: Inconsistent indentation.

Lines 281-282 appear to have less indentation than the surrounding object properties, though this may be a display artifact.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/views/DownloadsView/__tests__/DownloadsTable.test.tsx` around lines 280 -
283, Align the inconsistent indentation in the test object literal in
DownloadsTable.test.tsx: ensure the properties priority, queuePosition,
createdAt and the spread ...overrides are indented uniformly (matching
surrounding object properties) so the object is consistently formatted; locate
the object that sets priority: 5, queuePosition: 0, createdAt: Date.now(),
...overrides and fix the spacing to match the other properties.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src-tauri/src/application/commands/move_queue.rs`:
- Around line 99-107: The loop in move_queue.rs mutates each Download via
download.with_queue_position(...) and calls self.download_repo().save(...) per
item, which can leave the queue partially reordered if a later save fails; wrap
the whole reorder in a repository-level transaction or batch operation so all
saves are committed atomically (e.g., use a transaction API on
self.download_repo() or implement a batch_save/update_positions that accepts
cmd.ordered_ids and their new positions), perform all find_by_id and prepare
moved entities inside the transaction, then persist them together and rollback
on error to ensure atomicity.
- Around line 91-113: handle_reorder_queue currently renumbers only
cmd.ordered_ids starting at 1, which can produce duplicate queue_position values
for omitted (filtered-out) items; update the logic to fetch the full set of
queueable downloads from download_repo (e.g., all items with
queued/retry/waiting states), merge cmd.ordered_ids into that full list
preserving the relative order of omitted items, then iterate over the combined
ordered list assigning monotonic queue_position values (1..N) and saving each
via download_repo().save; keep publishing DomainEvent::QueueReordered with the
affected_ids derived from the final reordered list.

In `@src/views/DownloadsView/DownloadsTable.tsx`:
- Around line 683-705: The hooks that set up DnD (sortableIds via useMemo,
sensors via useSensors/useSensor, and handleDragEnd via useCallback) are
declared after early returns for loading/empty states which can change the hook
order and break React; move the declarations of sortableIds, sensors, and
handleDragEnd so they are executed before any early return paths (isLoading or
filteredDownloads.length === 0) so the hook order remains stable across renders,
keeping their current implementations and dependencies ([filteredDownloads,
reorderMut], etc.).

---

Nitpick comments:
In `@src-tauri/src/adapters/driven/event/tauri_bridge.rs`:
- Around line 60-61: Add explicit bridge-contract tests that assert the emitted
event name and payload shape for the new DomainEvent variants
DomainEvent::DownloadPrioritySet and DomainEvent::QueueReordered as mapped in
the event_name logic inside tauri_bridge.rs; create tests that construct each
DomainEvent variant, run it through the same serialization/mapping path used by
the bridge, and assert the resulting tuple (event_name string and JSON payload
keys/types) exactly matches the expected contract (e.g., "download-priority-set"
and "queue-reordered" and their required payload fields), mirroring the approach
used for existing events so any frontend contract regression fails the test.

In
`@src-tauri/src/adapters/driven/sqlite/migrations/m20260425_000004_add_queue_position.rs`:
- Around line 24-29: The created index idx_downloads_queue_position only covers
Downloads::QueuePosition; change it to a composite index on
(Downloads::QueuePosition, Downloads::CreatedAt) so default sorting by queue
position then creation time uses the index — update the Index::create() chain to
add .col(Downloads::CreatedAt) and rename the index (e.g.,
idx_downloads_queue_position_created_at) accordingly; apply the same change to
the other identical index creation block referenced (the one around lines
55-59).

In `@src-tauri/src/domain/event.rs`:
- Around line 112-124: The DownloadPrioritySet event currently carries a
primitive u8 for priority which bypasses domain validation; change its payload
to use the domain type Priority instead (i.e., DownloadPrioritySet { id:
DownloadId, priority: Priority }) so publishers/subscribers preserve invariants,
and update all constructors/emitters and match arms handling DownloadPrioritySet
to accept and propagate Priority rather than u8 (ensure any
conversions/validation occur at the boundary where raw values enter the system).

In `@src/views/DownloadsView/__tests__/DownloadsTable.test.tsx`:
- Around line 280-283: Align the inconsistent indentation in the test object
literal in DownloadsTable.test.tsx: ensure the properties priority,
queuePosition, createdAt and the spread ...overrides are indented uniformly
(matching surrounding object properties) so the object is consistently
formatted; locate the object that sets priority: 5, queuePosition: 0, createdAt:
Date.now(), ...overrides and fix the spacing to match the other properties.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1caa002c-80c7-45a7-81ef-22e8ef449e70

📥 Commits

Reviewing files that changed from the base of the PR and between 36c8516 and f01b453.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (27)
  • CHANGELOG.md
  • package.json
  • src-tauri/src/adapters/driven/event/tauri_bridge.rs
  • src-tauri/src/adapters/driven/logging/download_log_bridge.rs
  • src-tauri/src/adapters/driven/sqlite/download_read_repo.rs
  • src-tauri/src/adapters/driven/sqlite/download_repo.rs
  • src-tauri/src/adapters/driven/sqlite/entities/download.rs
  • src-tauri/src/adapters/driven/sqlite/migrations/m20260425_000004_add_queue_position.rs
  • src-tauri/src/adapters/driven/sqlite/migrations/mod.rs
  • src-tauri/src/adapters/driven/sqlite/progress_bridge.rs
  • src-tauri/src/adapters/driving/tauri_ipc.rs
  • src-tauri/src/application/commands/mod.rs
  • src-tauri/src/application/commands/move_queue.rs
  • src-tauri/src/application/commands/set_priority.rs
  • src-tauri/src/application/queries/get_downloads.rs
  • src-tauri/src/application/read_models/download_view.rs
  • src-tauri/src/application/services/queue_manager.rs
  • src-tauri/src/domain/event.rs
  • src-tauri/src/domain/model/download.rs
  • src-tauri/src/domain/model/views.rs
  • src-tauri/src/lib.rs
  • src/i18n/locales/en.json
  • src/i18n/locales/fr.json
  • src/types/download.ts
  • src/views/DownloadsView/DownloadsTable.tsx
  • src/views/DownloadsView/__tests__/DownloadsTable.test.tsx
  • src/views/DownloadsView/__tests__/DownloadsView.test.tsx

Comment thread src-tauri/src/application/commands/move_queue.rs
Comment thread src-tauri/src/application/commands/move_queue.rs Outdated
Comment thread src/views/DownloadsView/DownloadsTable.tsx Outdated
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 28 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src-tauri/src/application/commands/move_queue.rs">

<violation number="1" location="src-tauri/src/application/commands/move_queue.rs:100">
P1: `handle_reorder_queue` can partially persist position changes before failing on a later missing ID, leaving queue order in an inconsistent intermediate state.</violation>

<violation number="2" location="src-tauri/src/application/commands/move_queue.rs:104">
P1: Reordering renumbers only the submitted IDs to `1..N`, which can collide with `queue_position` values on omitted queued/retry/waiting downloads. Reindex against the full reorderable set (or require callers to submit the complete set) to preserve a consistent global queue order.</violation>
</file>

<file name="src/views/DownloadsView/DownloadsTable.tsx">

<violation number="1" location="src/views/DownloadsView/DownloadsTable.tsx:395">
P1: Depend on `sortable.setNodeRef` instead of the full `sortable` object to prevent constant ref recreation during drag.</violation>

<violation number="2" location="src/views/DownloadsView/DownloadsTable.tsx:684">
P1: Map `sortableIds` from the visually sorted `rows` instead of `filteredDownloads` to ensure dnd-kit's sorting strategies calculate correctly when column sorts are active.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src-tauri/src/application/commands/move_queue.rs Outdated
Comment thread src/views/DownloadsView/DownloadsTable.tsx Outdated
Comment thread src/views/DownloadsView/DownloadsTable.tsx Outdated
Comment thread src-tauri/src/application/commands/move_queue.rs
- Move dnd hooks before early returns in DownloadsTable to satisfy rules of hooks
- Build sortableIds from visually sorted rows (not filteredDownloads) so dnd-kit collision matches the rendered order
- Drop full sortable object from setRef deps; depend on setNodeRef only
- Reject move_to_top/move_to_bottom on non-reorderable states
- Use POSITION_STRIDE=1024 for top/bottom moves to avoid early i64 saturation
- Reorder: validate all submitted ids, merge omitted reorderable items into the renumbering so a filtered submission cannot leave duplicate queue_position values
- Add save_batch to DownloadRepository (default sequential impl) and override on SQLite with a transaction so reorder commits atomically
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src-tauri/src/application/commands/move_queue.rs">

<violation number="1" location="src-tauri/src/application/commands/move_queue.rs:162">
P2: Reject duplicate IDs in `ordered_ids`; currently duplicates are accepted and can produce inconsistent queue positions.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src-tauri/src/application/commands/move_queue.rs Outdated
@@ -183,6 +184,7 @@ impl Download {
downloaded_bytes: 0,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 New downloads skip reordered queue

Every download is created with queue_position: 0. After a user performs any reorder operation, existing reorderable items are assigned positions 1..N. A newly added download retains the default position 0, which sorts before all reordered items (0 < 1). This causes the new download to jump to the front of the scheduling queue regardless of the user's explicitly established order, violating the user's intent.

The QueueManager scheduling sort (b.priority()...then_with(|| a.queue_position()...)) will consistently pick the new 0-position item over all reordered items of equal priority.

Fix: when creating a download, compute and assign the max(current_queue_positions) + 1 (or an appropriate stride value), similar to handle_move_to_bottom, so it appends to the back of the current queue rather than hijacking position 0.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src-tauri/src/domain/model/download.rs
Line: 184

Comment:
**New downloads skip reordered queue**

Every download is created with `queue_position: 0`. After a user performs any reorder operation, existing reorderable items are assigned positions `1..N`. A newly added download retains the default position `0`, which sorts *before* all reordered items (`0 < 1`). This causes the new download to jump to the front of the scheduling queue regardless of the user's explicitly established order, violating the user's intent.

The `QueueManager` scheduling sort (`b.priority()...then_with(|| a.queue_position()...)`) will consistently pick the new 0-position item over all reordered items of equal priority.

Fix: when creating a download, compute and assign the `max(current_queue_positions) + 1` (or an appropriate stride value), similar to `handle_move_to_bottom`, so it appends to the back of the current queue rather than hijacking position 0.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code

- Reject duplicate ids in reorder_queue ordered_ids
- Assign back-of-queue position to new downloads (start_download, redownload) so they don't jump in front of items the user has explicitly reordered
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src-tauri/src/application/commands/move_queue.rs`:
- Around line 48-54: The current next_queue_position (and similar blocks at
lines 71-84 and 107-120) reads the current extremum then computes and persists a
new queue_position outside a transaction, which allows races; fix by moving the
extremum lookup + write into a single repository-level atomic operation or lock.
Add a new method on the DownloadRepository trait such as
allocate_next_queue_position(&self) -> Result<i64, AppError> (or a transactional
helper like with_transaction/execute_atomic that takes a closure) and implement
it so the repository performs the max(queue_position) query and inserts/returns
the new saturating_add(POSITION_STRIDE) value inside one DB transaction/lock;
then replace callers of next_queue_position (and the analogous
prev/insert-at-extreme logic in the other blocks) to call this new atomic
repository method instead of doing load_reorderable_pool() -> compute -> save
outside a transaction.
- Around line 176-180: The current sort uses (d.queue_position(), d.id().0)
which reorders items that legitimately share a queue_position; preserve the
existing omitted order by capturing the iteration order as a stable secondary
key instead of using id(). Replace the collection of omitted with an enumerated
collect (e.g., capture the original index from pool_index.values().enumerate()),
then sort_by_key using (d.queue_position(), original_index) and finally map back
to Vec<&Download>, referencing the existing symbols omitted, pool_index,
submitted, Download and the method queue_position() to locate where to change
the sort.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c61768f-7c1e-4a75-a196-7c04f192e069

📥 Commits

Reviewing files that changed from the base of the PR and between ef81c01 and 66fd4d5.

📒 Files selected for processing (3)
  • src-tauri/src/application/commands/move_queue.rs
  • src-tauri/src/application/commands/redownload.rs
  • src-tauri/src/application/commands/start_download.rs

Comment on lines +48 to +54
pub(crate) fn next_queue_position(repo: &dyn DownloadRepository) -> Result<i64, AppError> {
let pool = load_reorderable_pool(repo)?;
Ok(match pool.iter().map(|d| d.queue_position()).max() {
Some(m) => m.saturating_add(POSITION_STRIDE),
None => 0,
})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make extreme-position allocation atomic.

These paths all do read current min/max -> derive new queue_position -> save outside a transaction. Two concurrent create/move commands can therefore persist the same extreme value, so “append to back” / “move to top” / “move to bottom” stops being deterministic. Please move the extremum lookup and write behind one repository-level atomic operation or lock.

Also applies to: 71-84, 107-120

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/application/commands/move_queue.rs` around lines 48 - 54, The
current next_queue_position (and similar blocks at lines 71-84 and 107-120)
reads the current extremum then computes and persists a new queue_position
outside a transaction, which allows races; fix by moving the extremum lookup +
write into a single repository-level atomic operation or lock. Add a new method
on the DownloadRepository trait such as allocate_next_queue_position(&self) ->
Result<i64, AppError> (or a transactional helper like
with_transaction/execute_atomic that takes a closure) and implement it so the
repository performs the max(queue_position) query and inserts/returns the new
saturating_add(POSITION_STRIDE) value inside one DB transaction/lock; then
replace callers of next_queue_position (and the analogous prev/insert-at-extreme
logic in the other blocks) to call this new atomic repository method instead of
doing load_reorderable_pool() -> compute -> save outside a transaction.

Comment thread src-tauri/src/application/commands/move_queue.rs Outdated
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src-tauri/src/application/commands/move_queue.rs">

<violation number="1" location="src-tauri/src/application/commands/move_queue.rs:49">
P1: Compute-and-assign of the next queue position should be atomic with persistence; otherwise concurrent creates can allocate the same `queue_position` and break deterministic queue ordering.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src-tauri/src/application/commands/move_queue.rs
- Add CommandBus.queue_position_lock (tokio Mutex) so move_to_top, move_to_bottom, reorder_queue, start_download and redownload serialize their min/max read + persist; concurrent calls cannot allocate the same position any more
- Reorder: when omitted items share a queue_position, use the pool's original iteration order as the tie-breaker instead of id, so a filtered submit cannot silently reshuffle hidden downloads
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src-tauri/src/application/commands/start_download.rs (1)

561-564: Consider referencing the stride constant in the test assertion.

The assertion hardcodes 5 + 1024 which creates implicit coupling to POSITION_STRIDE. If the stride changes, this test will fail without obvious cause.

♻️ Suggested improvement
+        use crate::application::commands::move_queue::POSITION_STRIDE;
         assert_eq!(
             saved.queue_position(),
-            5 + 1024,
+            5 + POSITION_STRIDE,
             "new download must append after the highest existing reorderable position"
         );

Note: This requires making POSITION_STRIDE pub(crate) or exposing a test helper.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/application/commands/start_download.rs` around lines 561 - 564,
The test currently hardcodes the expected queue position as `5 + 1024`; change
it to reference the stride constant instead (e.g. `5 + POSITION_STRIDE`) so the
assertion uses the source-of-truth `POSITION_STRIDE` when checking
`saved.queue_position()`; to enable this, make `POSITION_STRIDE` reachable to
the test (e.g. change its visibility to `pub(crate)` or expose a small test-only
helper) and update the assertion to compute the expected value using that
symbol.
src-tauri/src/application/commands/move_queue.rs (1)

607-648: Test coverage note: multi-item tie-breaker scenario not fully exercised.

This test has only one omitted item (d2), so the tie-breaker between multiple omitted items sharing queue_position isn't validated. Consider adding a test with 2+ omitted items at the same position to confirm relative order preservation.

🧪 Additional test case
#[tokio::test]
async fn test_reorder_queue_preserves_multiple_omitted_items_order_on_tie() {
    // d1, d2, d3 all share queue_position 0. Submit only d1.
    // d2 and d3 must retain their relative insertion order.
    let repo = MockRepo::new()
        .with(make_download(1, 0))  // inserted first
        .with(make_download(2, 0))  // inserted second
        .with(make_download(3, 0)); // inserted third
    let events = Arc::new(RecordingBus::new());
    let bus = make_bus(repo, events);

    bus.handle_reorder_queue(ReorderQueueCommand {
        ordered_ids: vec![DownloadId(1)],
    })
    .await
    .unwrap();

    let d1 = bus.download_repo().find_by_id(DownloadId(1)).unwrap().unwrap();
    let d2 = bus.download_repo().find_by_id(DownloadId(2)).unwrap().unwrap();
    let d3 = bus.download_repo().find_by_id(DownloadId(3)).unwrap().unwrap();
    
    assert_eq!(d1.queue_position(), 1, "submitted item first");
    // d2 and d3 should follow in their original insertion order
    assert!(d2.queue_position() < d3.queue_position(), 
            "omitted items must preserve relative order");
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/application/commands/move_queue.rs` around lines 607 - 648, The
current test test_reorder_queue_preserves_omitted_order_on_position_tie only
covers one omitted item; add a second test (e.g.,
test_reorder_queue_preserves_multiple_omitted_items_order_on_tie) that creates
three MockRepo entries via make_download with the same queue_position (0), calls
bus.handle_reorder_queue(ReorderQueueCommand { ordered_ids: vec![DownloadId(1)]
}), then fetches items via bus.download_repo().find_by_id and asserts the
submitted item has the lowest queue_position and that the two omitted items
preserve their original relative order (e.g., assert!(d2.queue_position() <
d3.queue_position())). Use the same helpers (MockRepo, make_download,
RecordingBus, make_bus, DownloadId) so the test exercises the multi-omitted-item
tie-breaker.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src-tauri/src/application/commands/move_queue.rs`:
- Around line 607-648: The current test
test_reorder_queue_preserves_omitted_order_on_position_tie only covers one
omitted item; add a second test (e.g.,
test_reorder_queue_preserves_multiple_omitted_items_order_on_tie) that creates
three MockRepo entries via make_download with the same queue_position (0), calls
bus.handle_reorder_queue(ReorderQueueCommand { ordered_ids: vec![DownloadId(1)]
}), then fetches items via bus.download_repo().find_by_id and asserts the
submitted item has the lowest queue_position and that the two omitted items
preserve their original relative order (e.g., assert!(d2.queue_position() <
d3.queue_position())). Use the same helpers (MockRepo, make_download,
RecordingBus, make_bus, DownloadId) so the test exercises the multi-omitted-item
tie-breaker.

In `@src-tauri/src/application/commands/start_download.rs`:
- Around line 561-564: The test currently hardcodes the expected queue position
as `5 + 1024`; change it to reference the stride constant instead (e.g. `5 +
POSITION_STRIDE`) so the assertion uses the source-of-truth `POSITION_STRIDE`
when checking `saved.queue_position()`; to enable this, make `POSITION_STRIDE`
reachable to the test (e.g. change its visibility to `pub(crate)` or expose a
small test-only helper) and update the assertion to compute the expected value
using that symbol.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d79e8932-3f90-431b-9b50-10da8db67c03

📥 Commits

Reviewing files that changed from the base of the PR and between 66fd4d5 and 37a95c0.

📒 Files selected for processing (4)
  • src-tauri/src/application/command_bus.rs
  • src-tauri/src/application/commands/move_queue.rs
  • src-tauri/src/application/commands/redownload.rs
  • src-tauri/src/application/commands/start_download.rs

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src-tauri/src/application/commands/move_queue.rs">

<violation number="1" location="src-tauri/src/application/commands/move_queue.rs:192">
P2: Tie-breaking omitted items by pool index is nondeterministic because `find_by_state` does not enforce SQL ordering. Equal `queue_position` items can be reshuffled unpredictably.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

.enumerate()
.filter(|(_, d)| !submitted.contains(&d.id()))
.collect();
omitted.sort_by_key(|(idx, d)| (d.queue_position(), *idx));
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Tie-breaking omitted items by pool index is nondeterministic because find_by_state does not enforce SQL ordering. Equal queue_position items can be reshuffled unpredictably.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src-tauri/src/application/commands/move_queue.rs, line 192:

<comment>Tie-breaking omitted items by pool index is nondeterministic because `find_by_state` does not enforce SQL ordering. Equal `queue_position` items can be reshuffled unpredictably.</comment>

<file context>
@@ -170,22 +180,24 @@ impl CommandBus {
+            .filter(|(_, d)| !submitted.contains(&d.id()))
             .collect();
-        omitted.sort_by_key(|d| (d.queue_position(), d.id().0));
+        omitted.sort_by_key(|(idx, d)| (d.queue_position(), *idx));
 
-        let mut final_order: Vec<&Download> = Vec::with_capacity(pool_index.len());
</file context>
Fix with Cubic

…eaker

PR #105 review pointed out the reorder tie-breaker relied on `find_by_state` iteration order, which had no SQL ORDER BY clause. Add `(queue_position ASC, created_at ASC, id ASC)` to the SQLite query so the pool order is stable across processes; mirror the same ordering in the in-memory MockRepo used by command tests.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src-tauri/src/application/commands/move_queue.rs (2)

173-178: Minor: Redundant reorderability check for items already in the reorderable pool.

Items in pool_index come from load_reorderable_pool which only loads REORDERABLE_STATES, so is_reorderable(download.state()) at line 173 will always return true. This check is defensive but could be removed for clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/application/commands/move_queue.rs` around lines 173 - 178, The
check guarding is_reorderable(download.state()) is redundant because items in
pool_index are populated by load_reorderable_pool which already filters to
REORDERABLE_STATES; remove the entire if-block that returns AppError in
move_queue.rs (the block referencing is_reorderable(download.state()) and
AppError::Validation) and either rely on the existing pool invariants or replace
it with a debug_assert!(is_reorderable(download.state())) for defensive
debug-time validation; ensure you update any surrounding logic that assumed the
early Err return is possible.

849-871: Consider adding a symmetric test for move_to_bottom saturating at i64::MAX.

There's a test for move_to_top saturating at i64::MIN, but no corresponding test for move_to_bottom at i64::MAX. While the saturating_add at line 120 handles this correctly, a test would document the behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/application/commands/move_queue.rs` around lines 849 - 871, Add
a new async #[tokio::test] named something like
test_move_to_bottom_saturates_at_max_i64 that mirrors
test_move_to_top_saturates_at_min_i64: create a MockRepo with make_download(1,
i64::MAX) and make_download(2, 0), create events/ bus via RecordingBus::new()
and make_bus, call bus.handle_move_to_bottom(MoveToBottomCommand { id:
DownloadId(2) }).await.unwrap(), then fetch the moved entry via
bus.download_repo().find_by_id(DownloadId(2)).unwrap().unwrap() and assert that
moved.queue_position() == i64::MAX to verify saturation without overflow;
reference MoveToBottomCommand, DownloadId, make_download, MockRepo,
RecordingBus, make_bus, and queue_position() when adding the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src-tauri/src/application/commands/move_queue.rs`:
- Around line 173-178: The check guarding is_reorderable(download.state()) is
redundant because items in pool_index are populated by load_reorderable_pool
which already filters to REORDERABLE_STATES; remove the entire if-block that
returns AppError in move_queue.rs (the block referencing
is_reorderable(download.state()) and AppError::Validation) and either rely on
the existing pool invariants or replace it with a
debug_assert!(is_reorderable(download.state())) for defensive debug-time
validation; ensure you update any surrounding logic that assumed the early Err
return is possible.
- Around line 849-871: Add a new async #[tokio::test] named something like
test_move_to_bottom_saturates_at_max_i64 that mirrors
test_move_to_top_saturates_at_min_i64: create a MockRepo with make_download(1,
i64::MAX) and make_download(2, 0), create events/ bus via RecordingBus::new()
and make_bus, call bus.handle_move_to_bottom(MoveToBottomCommand { id:
DownloadId(2) }).await.unwrap(), then fetch the moved entry via
bus.download_repo().find_by_id(DownloadId(2)).unwrap().unwrap() and assert that
moved.queue_position() == i64::MAX to verify saturation without overflow;
reference MoveToBottomCommand, DownloadId, make_download, MockRepo,
RecordingBus, make_bus, and queue_position() when adding the test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 58c78350-cd15-4f5e-96d4-e56494002da4

📥 Commits

Reviewing files that changed from the base of the PR and between 37a95c0 and d91a2e9.

📒 Files selected for processing (2)
  • src-tauri/src/adapters/driven/sqlite/download_repo.rs
  • src-tauri/src/application/commands/move_queue.rs

@mpiton mpiton merged commit 55b22ff into main Apr 25, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant