feat(files): multi-select files with bulk move/delete on the audiobook detail page#720
Draft
kevinheneveld wants to merge 2 commits into
Draft
feat(files): multi-select files with bulk move/delete on the audiobook detail page#720kevinheneveld wants to merge 2 commits into
kevinheneveld wants to merge 2 commits into
Conversation
When files actually belong to a different book the library already
tracks (two books' tracks imported onto one record, or a collection
being split into its real books), a "Move Files to Another Book" action
on the detail page opens a transfer dialog with per-file checkboxes and
a library search ranked by token overlap.
POST /library/{id}/files/transfer: DB ownership reassigns always; the
physical file moves into the destination folder best-effort (failures
leave it in place with a warning — Organize can relocate later).
Collisions with a row the target already owns at the same path are
detected BEFORE any disk move and skipped with a warning, and any
per-file DB failure becomes a warning rather than aborting the whole
transfer. A source left without audio gets its legacy single-file
columns reset; history entries land on both records.
Row reassignment uses a targeted IAudiobookFileRepository.ReassignAsync
(detached stub, only the reassigned columns marked modified) so bulk
transfers can't trip EF identity-map conflicts on overlapping
navigation graphs.
Warning/error toasts are now sticky by default (dismissed via the close
button) — transfer warnings carry information the user needs to read
and act on; info/success keep the 5s auto-dismiss.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…k detail page
Deleting duplicates or moving a subset of files one at a time doesn't
scale (e.g. a duplicated book: the same audio under two different
filename schemes on one record). Add a selection checkbox to each file
row (shift-click extends a range) and a toolbar with select-all, "Move
selected…" and "Delete selected". Bulk move hands the selection to
TransferFilesModal via its initialFileIds preselection; bulk delete
loops a per-file delete behind one confirm with a shared "also delete
from disk" option and a summary toast. Selection clears on reload.
The per-file delete this rides on is new here too:
DELETE /library/{id}/files/{fileId}?deleteFromDisk= — removes the
AudiobookFile row (ownership guarded: the file must belong to the
addressed audiobook), optionally deletes the file from disk
(disk-delete failures surface as warnings; the row is still removed),
and records a "File Removed" history entry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds multi-select to the Files list on the audiobook detail page: a selection checkbox per file row (shift-click extends a range) and a toolbar with select-all, Move selected… and Delete selected.
TransferFilesModal(from feat(library): move files to another audiobook record #719) via itsinitialFileIdspreselection — pick a destination record and only the ticked files transfer.DELETE /library/{id}/files/{fileId}?deleteFromDisk=(LibraryFileDeleteWorkflow+IAudiobookFileService.DeleteAudiobookFileAsync): ownership-guarded (the file must belong to the addressed audiobook), disk-delete failures surface as warnings while the DB row is still removed, and a "File Removed" history entry records each deletion.Why
Deleting duplicates one at a time doesn't scale — the motivating live case was a duplicated book: the same audio under two different filename schemes sitting on one record, which a split-by-content flow can't separate (it's the same book twice). Select the redundant copies → delete once.
Tests
6 new tests in
LibraryController_FileDeleteTests(missing audiobook/file, wrong-owner guard, DB-only delete keeps the file on disk, disk delete, row-only ghost cleanup). Full suite: 1030/1030 passing.vue-tsc+ eslint + prettier clean.🤖 Generated with Claude Code