Skip to content

feat(ui): add bulk retry, cancel and delete to the downloads table - #101

Closed
khaosdoctor wants to merge 1 commit into
roziscoding:mainfrom
khaosdoctor:feat/downloads-bulk-actions
Closed

khaosdoctor wants to merge 1 commit into
roziscoding:mainfrom
khaosdoctor:feat/downloads-bulk-actions

Conversation

@khaosdoctor

Copy link
Copy Markdown

What does this PR do?

Adds a checkbox column to the Downloads table and a checkbox on each mobile card. Selecting rows shows a bar with Retry, Cancel and Delete, each labelled with how many selected rows it applies to; rows that don't support an action are skipped. Cancel and delete ask for confirmation, like the per-row buttons.

  • Requests run in parallel through the existing per-download endpoints, so there is no new API.
  • Rows that fail stay selected, and the toast reports how many failed.
  • Changing a filter clears the selection, so a bulk action never touches rows the filters hide.

Related issue

None.

Checklist

  • Commits follow Conventional Commits
  • Commits are signed (GitHub enforces this — unsigned commits are rejected)
  • mise run lint:fix was run and its output is committed
  • mise run test passes (and mise run test:e2e if transfer/request paths changed)
  • Docs updated if behavior or configuration changed (no docs change needed)

Copilot AI balanced review requested due to automatic review settings November 8, 2026 21:07
@greptile-apps

greptile-apps Bot commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change is limited to bulk actions on the Downloads page.

Fix bulk Delete reaching rows hidden by live updates before merging.

Summary

Adds bulk Retry, Cancel, and Delete to the downloads table and mobile cards.

  • The Downloads page lets users run actions on selected downloads.

Reviews (1) · Last reviewed commit: "feat(ui): add bulk retry, cancel and del..." · Reviewed by Greptile

// Row selection for bulk actions, keyed by download id (see get-row-id) so it
// survives live updates and paging.
const rowSelection = ref<Record<string, boolean>>({})
const selected = computed(() => (data.value?.downloads ?? []).filter(d => rowSelection.value[String(d.id)]))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Delete reaches hidden downloads

selected reads all downloads, not the filtered list. Select a downloading row under the Downloading filter, then let it finish. The live update hides the row without changing the filter, so it stays selected and bulk Delete still removes its record and file. Build the targets from selected rows that still match filtered, while keeping selection across pages.

Knowledge Base Used: Nuxt management dashboard

Fix in Claude Code Fix in Codex

return

bulkPending.value = action
const results = await Promise.allSettled(targets.map(d => sendAction(d, action)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Deleted downloads leave files behind

Promise.allSettled sends deletes together, but the backend only orders deletes by download ID. Two failed or queued records can share a file. If both deletes check for siblings before either record is removed, each keeps the shared file, then both remove their records and report success. Order deletes that share a path, or make the backend order them by path, so the last delete removes the file.

Fix in Claude Code Fix in Codex

// It also drops the selection, so a bulk action never hits rows the filters hide.
watch([filter, search, peerFilter, codeFilter], () => {
page.value = 1
rowSelection.value = {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Failed rows lose their selection

The new selection reset also runs when keepPresent drops a filter option after a successful action. For example, filter by peers A and B, select their last records, and bulk Delete. If A succeeds and B fails, A disappears from the peer options and this watcher clears B's selection too. Keep failed rows selected when filter options are removed automatically; reserve the full reset for user changes.

Fix in Claude Code Fix in Codex

Comment on lines +126 to +128
bulkPending.value = action
const results = await Promise.allSettled(targets.map(d => sendAction(d, action)))
bulkPending.value = undefined

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Successful actions show failure messages

runBulkAction bypasses pendingActions, so row buttons remain enabled for downloads already in a bulk request. Bulk actions can also include a row whose own request is still running. A second Delete can return not found after the first succeeds, producing a failure toast for a successful deletion. Use the same per-download pending state in both paths and skip rows already busy.

Fix in Claude Code Fix in Codex

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Bulk actions can delete completed artifacts from selected rows hidden by live filter updates and overlap with in-flight per-row requests, allowing duplicate or conflicting actions.

2 open issues
What changed in this PR

Adds bulk download actions to jack's desktop table and mobile cards, using
the existing per-download endpoints without API changes.

Changes:

  • Adds row selection with eligibility counts for Retry, Cancel and Delete.
  • Runs requests in parallel, confirms destructive actions, retains failed
    selections and clears selection when filters change.
File Description
apps/​ui/​app/​pages/​downloads.vue Adds selection controls and bulk action handling; shares request logic with per-row actions.

Review effort: Balanced

// Row selection for bulk actions, keyed by download id (see get-row-id) so it
// survives live updates and paging.
const rowSelection = ref<Record<string, boolean>>({})
const selected = computed(() => (data.value?.downloads ?? []).filter(d => rowSelection.value[String(d.id)]))
return

bulkPending.value = action
const results = await Promise.allSettled(targets.map(d => sendAction(d, action)))
@khaosdoctor

Copy link
Copy Markdown
Author

Merged into #102 at the maintainer's request, so it can be reviewed as one PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants