feat: the library's albums go on the wanted list as held, once; after that the list is the truth (M502 #5567)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m24s
release / go (push) Successful in 1m44s
release / integration (push) Successful in 5m19s
release / android (push) Successful in 5m31s
release / Build signed APK (releases and dev) (push) Successful in 5m35s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped

A one-time job in the wanted worker lists every album in the library as a
held want, one per release group, with a record that it happened
(acquisition_settings.library_listed_at). Albums whose tags name only a
release are named on MusicBrainz first, 120 a pass; what nothing names is
counted and shown on the Held tab.

Re-acquisition now wants back only albums on the list. Taking a held album
off the list leaves its files, and it is never fetched back; albums put in
the library folder by hand afterwards are not added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 16:13:46 -04:00
co-authored by Claude Opus 5.5
parent bc198a9389
commit 2c677b3184
19 changed files with 965 additions and 172 deletions
+37 -2
View File
@@ -13,7 +13,9 @@
// One tab of the Wanted view (M502 #5520–#5522): the albums still wanted,
// the missing albums wanted back, or the ones held. Each open want says
// what its last search found and how its download is getting on; Releases
// lists every candidate with its verdict, and Search runs one now.
// lists every candidate with its verdict, and Search runs one now. The
// library's albums were put on the list as held once (#5567); taking one
// off leaves its files and stops it being fetched back if they go.
let { list }: { list: WantState } = $props();
@@ -21,6 +23,7 @@
const wantsStore = $derived(createWantsQuery(list));
const wants = $derived($wantsStore);
const items = $derived((wants.data?.pages ?? []).flatMap((p) => p.items) as Want[]);
const library = $derived(wants.data?.pages[0]?.library);
let busy = $state<string | null>(null);
let searching = $state<string | null>(null);
@@ -30,7 +33,11 @@
busy = w.id;
try {
await removeWant(w.id);
pushToast(`${w.title} is off the wanted list.`);
pushToast(
w.held_at
? `${w.title} is off the list. Its files stay, and it won't be fetched back if they go missing.`
: `${w.title} is off the wanted list.`
);
await client.invalidateQueries({ queryKey: ['adminWants'] });
} catch (e) {
pushToast(errMessage(e), 'error');
@@ -137,6 +144,7 @@
function why(w: Want): string {
if (w.reason === 'request') return w.requested_by ? `Requested by ${w.requested_by}` : 'Requested';
if (w.reason === 'follow') return 'New from a followed artist';
if (w.reason === 'library') return 'In your library when the list began';
return 'Its files went missing';
}
@@ -151,6 +159,22 @@
}
</script>
{#if list === 'held' && library}
{#if !library.listed_at}
<p class="mb-3 text-sm text-text-secondary" data-testid="library-listing">
Your library's albums are being added to this list as held. Albums whose tags name only a
release are looked up on MusicBrainz first, about one a second.
</p>
{:else if library.unlisted > 0}
<p class="mb-3 text-sm text-text-secondary" data-testid="library-listing">
{library.unlisted === 1 ? '1 album' : `${library.unlisted} albums`} in your library
{library.unlisted === 1 ? "isn't" : "aren't"} on this list: nothing in {library.unlisted === 1 ? 'its' : 'their'}
tags names {library.unlisted === 1 ? 'it' : 'them'} to MusicBrainz. Those are never fetched back if
their files go missing.
</p>
{/if}
{/if}
{#if wants.isPending}
<p class="text-text-secondary">Reading the wanted list…</p>
{:else if wants.isError && !wants.isFetchNextPageError}
@@ -216,6 +240,17 @@
<div class="truncate text-xs text-text-secondary" data-testid="want-arrived">{arrived(w)}</div>
{/if}
</div>
{#if w.held_at}
<button
type="button"
onclick={() => onRemove(w)}
disabled={busy === w.id}
aria-label="Take {w.title} off the list"
class="inline-flex shrink-0 items-center gap-1 rounded-md border border-border px-2.5 py-1 text-xs text-text-secondary hover:bg-surface-hover hover:text-text-primary disabled:opacity-50"
>
<X size={12} strokeWidth={2} /> Remove
</button>
{/if}
{#if !w.held_at}
<div class="flex shrink-0 flex-wrap justify-end gap-1.5">
<button