feat(qt): find an available version for a track Qobuz pulled

Reachable from a dead row's context menu, in a playlist the user owns. The
reference had this and shipped it with four defects; none are ported.

It was unranked -- Qobuz's raw relevance order shown to the human. The weighted
matcher that already lives in qbz-playlist-import does the ranking now, and its
ISRC short-circuit matters most: licensing churn usually republishes the SAME
recording under a new id with the SAME ISRC, and the pulled track keeps its
isrc (confirmed in the 2026-08-17 capture of album 0886443985094). That case
needs no human judgement, so the ISRC lookup runs first and pins its hit to the
top.

It removed before it added, so a failed add lost the track outright. The order
here is add, reposition, remove.

It did not preserve position -- the computed index went to a console.log and
the replacement landed at the end. It lands where the dead row was, through the
new updateTracksPosition verb. Every failure in that step is a warning and a
different success toast: the repair still stands at the end of the playlist,
exactly where the reference always put it. Position is a nicety, never a reason
to abort a repair.

Two guards. If the ISRC lookup answers with the dead track itself -- it can,
since a pulled track keeps its metadata and search still indexes it -- then
"replace" would be a no_duplicate no-op followed by a remove, and the user
would lose the row while being told it was repaired. And when the remove fails
the playlist permanently holds both rows: that is not rolled back, because a
rollback is a second write that can fail the same way and would delete the only
good copy. The list is refreshed so the user SEES both, and the toast says the
old one has to go by hand rather than claiming a reload will fix it.

cc
This commit is contained in:
vicrodh committed 2026-08-17 14:41:39 -06:00
1 parent 7780efa5db
commit b2c74c4140
7 files changed
+1310 -1

No files matched your search

+7
View File
@@ -340,6 +340,13 @@ ApplicationWindow {
// importer makes (the whole progress panel, the log, the summary) is
// dropped on the floor with nothing logged on either side.
QbzPlaylistImport.boot()
// "Find available version" (2026-08-17 unavailable-tracks contract §6).
// Nothing to seed — the modal is closed until a playlist row's context
// menu opens it — but without this line every publish the controller
// makes is dropped on the floor: the modal would open empty and keep
// spinning, since its `loading` flag is only ever taken down by a
// publish.
QbzTrackReplace.boot()
// HiFi Wizard. Nothing to seed — the modal is closed until the
// Settings > Audio row opens it — but without this line every publish
// the wizard makes is dropped on the floor: the health verdict, the