Skip to content

[Step][#1080] Repair: point books at the right Audible record, from the book page and the library - #1054

Open
Yoldark34 wants to merge 127 commits into
Listenarrs:canaryfrom
Yoldark34:wip/repair-books
Open

Yoldark34 wants to merge 127 commits into
Listenarrs:canaryfrom
Yoldark34:wip/repair-books

Conversation

@Yoldark34

@Yoldark34 Yoldark34 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

First step of the Repair epic, #1080. Also covers the Repair item of #1077.

Every rule the screen applies is written out in the epic, in the "Rules (the front's business logic)" block of this step: search priority, search rules, results and their tags, picking, buttons, automatic actions and shortcuts, disabled controls, states, selection, repairing, the results step and the layout. This description sums them up.

Why

A book already in the library can point at the wrong Audible record: another edition, another volume, or the same book in another region's catalog. Rescan metadata can't fix it, because it keeps rescanning that record. This PR adds the buttons to point a book at the right record.

Find Match is affected too

Repair and Library Import's Find Match now use the same component, a book card (BookMatchCard.vue), because they do the same thing: find the Audible record a book should point at. Find Match shows the folder's book in that card, without the checkbox, and "Use this match" takes the record found. It always opens with the same search, from the folder's scan, in the configured region, whatever match the folder already has: that match only shows as Current. When the scan found an ASIN, it is in the ASIN field and searched as an ASIN, as Start Matching does ("No record for … in Audible France" when the region doesn't sell it), with the scan's title and author ready for a search without it; otherwise the scan's title and author are searched. A pick from an ASIN search carries the record's series, genres and dates (Add New's ASIN search now keeps them from the record), so the book is imported with them. Everything below about the card applies to Find Match as well.

Change

Where

  • A Repair button on the book page, and "Repair Selected (n)" in the library, both orange, open the Repair dialog.

Three steps, each named in the dialog's title: "Repair Books - Matching - Step 1/3", "Repair Books - Repair process - Step 2/3", then the Organize dialog opened from it, "Repair Books - Organize - Step 3/3".

Step 1, matching: each book is a card

  • ASIN, Title, Author and Region, then Search; erase buttons in the fields. Current and New under them (ASIN - Title: Subtitle - Authors - Region), with room kept for their usual lines. Once a record is picked, New shows in green the parts that change, and Current in orange the parts they replace (the same record picked again, to read its metadata anew, highlights nothing).
  • The results' header line is always there: "No search request done" until a search, then Results for … in France (FR) - Request: 1.23 s (or - Cached results), or No results for … (orange when nothing was found or the search was canceled, red when it failed).
  • Why there are no results is said in the middle of the empty list, once the spinner is gone: the request's error (red), "No record for … in Audible …", "Search canceled" or "Not an ASIN …" (orange). An ASIN that isn't one empties the results like a failed search. The New line only ever shows a picked record, or its default text.
  • Results tags: Current (green, the book's ASIN in its region), Other region (grey, the same ASIN in another catalogue, with a tooltip), and each record's language (green when it is the preferred search language, grey otherwise or unknown).
  • No layout shift: a card alone in its dialog (Find Match, Repair of one book) keeps the list's room from the start; with several books, a first search opens an empty list at least one result high, measured on an unseen result with every line a result can have, so a short answer doesn't move what is under the book; a failed or canceled search empties the results and leaves an empty list of the same height.

Searching

  • Add New's search, in the chosen region only, without a language filter (Audible France also sells English editions; each result shows its language).
  • A filled ASIN has priority, otherwise the title and author: Search, Enter in any field and a region change all follow that priority. Typing never searches; changing the region is the one automatic search (the happy path: the right ASIN in the wrong region).
  • Answered searches are kept while the dialog is open, shared by its books: the same values again are served without a request. Search becomes Retry after every search until a field changes; Retry always asks Audible again.
  • Loading and reveal: while a search runs, the previous results are greyed out under a spinner saying what they wait for, and can't be picked. Once results arrive, "Found 3 results" stays readable for 300 ms, then fades out over 200 ms; picking unlocks after it.
  • One search at a time (a known limit, addressed later in [Epic] Repair books that point at the wrong Audible record #1080): every other card is locked and its results greyed out ("Waiting: a search is running on another book"). Cancel search stops a search; it times out after 30 seconds.
  • Only the books it was opened for last: opened again for other books while the previous ones still load (the library keeps one dialog), it shows only the last ones and loads until they are in; closed while loading, nothing that answers later fills it.
  • A book that can't be loaded doesn't block the others: books load one at a time; one that fails (deleted since it was selected, a request timing out) gets its own card in the error state, saying why ("Not found: deleted since it was selected", "Couldn't be loaded: …"), which can't be ticked and counts as skipped. Load again reads it anew, except a deleted book. When none can be loaded, the dialog says why.
  • Repair asks first ("Repair 3 books?", Cancel or Repair 3), since a repair can't be undone: each book takes its new record as a whole (its details replaced, what the record lacks emptied), changes made by hand are lost, the files, folder and the book's settings stay. Cancel, or Escape, goes back to step 1 as it was. Retry doesn't ask again. If the page is left while it asks (browser Back, a redirect), the question goes with the dialog and nothing is repaired.
  • Escape on a confirmation shown over the dialog answers it only (this question, or leaving the page): every open dialog heard it, so Repair Books closed too, losing the picks or step 2's results.
  • A record without an ASIN (when Audible has nothing, the server adds records from another source, OpenLibrary): Find Match can pick and use it, as canary did, and Library Import adds the book from it. Repair can't: it points a book at a record a metadata provider can read again (an Audible record today), so such a result is greyed, tagged "No ASIN", with the reason, and can't be picked.
  • A card alone in its dialog keeps the room its results fit in (Find Match, Repair with one book): the room left by the rest of the dialog, at least two results high (the list scrolls inside past that), measured when the card shows and when the window is resized, before any search answers, so the dialog doesn't scroll and nothing moves when results arrive. With several books, the results open under each book as before.
  • One book: no Selection block: its own checkbox says it all (several books keep "n of m book(s) to repair" with Select All and Clear).

States: a bar along each card's bottom (grey ignored, green matched, orange invalid, red error, blue breathing while searching), with the legend in the footer, centred on the dialog, moved aside only as far as Cancel and Repair need, and on its own line above them (still centred) when the line is too short (the cards then take the narrow layout). The dialog's bottom bar is unseen on step 1, its room kept.

Step 2, the repair process

  • Only matched books are repaired, opening on "Repair 0"; picking a record ticks the book. Repair moves to step 2 at once, which holds only the books to repair (the unticked ones stay behind) and keeps the Selection section, now for Organize.
  • Each book's row keeps its height: its title, then the record it points at (the one it is to get while it waits, the one it got once repaired, its old one when the repair failed or was canceled), then where its repair is: a spinner with "Waiting to be repaired" or "Repairing…", then "Repaired in 0.5 s", "The new ASIN is saved, but the metadata rescan failed …" (orange) or "Repair failed …" (red). Show details / Hide details (same size either way) opens the Old and New records, the replaced parts in orange and the new ones in green; its tooltip gives both records on two lines without opening.
  • A repaired book takes its record as a whole: the rescan only writes what the record has, so the book's details (series and number, subtitle, description, publisher, dates, language, version, edition, narrators, genres, ISBNs) are emptied first, on every repair (a rescan of the same record too), then the rescan fills what the record has. The title, authors, cover and runtime are always read again, so they stay until then: a failed rescan never leaves a book without a title. When a repair stops part way, its row says what was saved ("The new ASIN is saved and its details emptied, but the metadata rescan failed …", "Its details are emptied, but …", "The new ASIN is saved, but its old details couldn't be emptied …"). Without it, a book kept the old record's series and Organize filed it under it. The server should do this itself: [Epic] Book metadata: what the provider manages, what the user manages, and when a book is rescanned #1125 (book metadata epic).
  • The ASIN a book is repaired away from is dropped, whatever its source: the server stores every ASIN a repair saves as Manual, so an earlier repair's would stay as a fallback the rescan reads. Other Manual identifiers (an ASIN added for another region, an ISBN typed by hand) stay.
  • Once the rescan has answered, the book is repaired: the book the rescan answers with is shown when the answer holds it (Metadata rescan: answer with the saved book, so callers don't have to read it back #1128), with no other request; until then the book is read back, and a read that fails only leaves the row showing the picked record ("Repaired in 1.2 s; the book couldn't be read back to show it: …"), still green and ticked for Organize. The row shows the subtitle and authors the server has. Before, a failed read was reported as a failed rescan, in orange.
  • A rescan refused for coming too soon waits: the server rescans a book once every 15 s, and 5 times in 10 minutes. The row counts down ("Waiting 12 s: a book can be rescanned once every 15 s"), then the rescan is asked once more; a longer wait fails at once, saying when to retry ("too many rescans of this book, retry in about 8 min"). Cancel also stops a book waiting there.
  • A rescan that read another record than the picked one says so: when the picked record fails, the server reads the book's other identifiers (its other ASINs, then ISBNs). The row then ends in orange, "The rescan read another record (ASIN - Region) than the one picked: check the book", and the book isn't ticked for Organize.
  • One repair at a time: each row's bar breathes blue while its book is repaired, then turns green, orange or red. The dialog's bottom bar fills as each repair ends, breathing until the last one; legend for the rows' bars on its own line above the buttons.
  • Any problem turns the bottom bar orange and is said between Close and the skipped count ("Not all repaired: 1 failed, 1 repaired in part, 1 canceled"), in a place kept from the start of step 2 (above the buttons when its longest text doesn't fit).
  • Cancel is red while the repairs run: the book being repaired ends first, the ones still waiting are canceled at once. Rolling back what a repair changed is a later step of [Epic] Repair books that point at the wrong Audible record #1080. Organize waits for the repairs to end; a repaired book is ticked for it as soon as it is done.
  • Leaving the page while the repairs run asks first ("Leave while books are repaired?", Leave or Stay), shown on top of the Repair dialog (the shared confirm dialog now sits above any dialog that asks it: it came under it), since they run in the browser. The repairs hold while it asks: the book being repaired ends, the next one waits for the answer. Stay goes on with them; Leave cancels the books still waiting, as Cancel does. Closing or reloading the tab gets the browser's own warning; if the dialog goes anyway, the waiting books are left out, so nothing keeps running unseen. Leave chosen after the last repair ended under the question leaves nothing paused: if the page then stays (its navigation failed, e.g. the page's code couldn't load after an update), the next Repair runs normally. Only Cancel stops a book waiting for the rescan cooldown (its row says so, with Retry); Leave, or the dialog going, lets it finish its rescan, as the question promises.
  • Retry at the bottom right of a failed row, or of one done in part, repairs that book again (red Cancel and Organize waiting meanwhile). It sits in room the row always keeps free, so showing it moves nothing.
  • An unexpected error ends the run: the books not repaired yet end failed ("Repair stopped: …"), shown on step 2 and retryable; the dialog never stays repairing.
  • Back, once the repairs are over (in place of Close, the same size as Cancel): step 1 loaded as when the dialog opens, each book as the repairs left it. Closing the dialog after a repair saved something refreshes the page behind, even from step 1.
  • No shift: the message at the top keeps the height of its longest version; with one book, step 2 keeps step 1's height (an empty room under the row).

Step 3, Organize: "Organize n" opens the Organize dialog, titled as Repair's step 3, on the ticked repaired books (their files were organized with the old metadata, which the message at the top of step 2 says). Its Cancel is Back there: step 2 again, as it was left. It opens at once: Organize reads its previews from the server, and the page behind (the library, or the book, kept shown) is refreshed meanwhile. Before, it waited for the whole library to be read again, with no dialog in between.

Known limit: the repairs run in the browser tab. No lock between tabs (another tab goes stale; the same book repaired in two tabs keeps the last save), and a tab closed mid-repair, past the browser's warning, may leave the book being repaired done in part, which a Rescan finishes. The "Repairs as backend tasks" step of #1080 removes it.

Known limit: one book at a time, in the browser. Books are loaded, then repaired, one after the other (no backend task yet), so a large selection takes a while, and a selection of hundreds of books makes a dialog with hundreds of cards. Repair is meant for the odd book that points at the wrong record, not for the whole library. The "Repairs as backend tasks" step of #1080 lifts it.

Known limit: Repair trusts the server's rescan. When Audible gives an empty record for the picked ASIN at the time of the rescan, the server still answers success and the book keeps its old metadata, so the row says Repaired: #1124.

Known issue, from Add New's search: a title or author holding "Series:", "Author:", "Title:", "ISBN:" or "ASIN:" is searched wrongly (#1127). The cards search exactly like Add New, whose search splits such a title on that word as if it were a search keyword ("The Wheel of Time Series: Book 1" is searched as the series "Book 1"). Canary's Find Match sent the title and author apart, so this PR brings the problem there; it is the price of one shared search instead of three, which also makes the move to the backend search (#1076) one change. Fixing Add New's search fixes Add New, Find Match and Repair at once.

Known issue, already on canary: the page scrolls behind an open dialog (the shared Modal doesn't lock it). Not caused nor fixed here; it needs its own issue and a fix in the shared Modal.

Not in this PR

Tests

RepairBooksModal.spec.ts, LibraryImportSearchModal.spec.ts and FindMatchResults.spec.ts cover the rules above: search priority and its triggers, the answered searches and Retry, the header line and its sources, loading and reveal timings (fake clock), placeholders and kept heights, the tags, one search at a time, the footer layout, the highlighted parts, step 2 (one repair at a time, Cancel, Retry, Back, the warning, details, the kept heights, leaving the page and the repairs held while it asks, the rescan cooldown, a rescan that read another record, only the latest load, a book that can't be loaded, the details emptied, the question before repairing, Escape on a question over the dialog) and Organize's step 3 (its title, Back). Frontend: 828 passed; type-check, eslint and prettier are clean. No backend change.

End to end, on a real instance: five scenarios drive the real app in Chrome (Playwright), on a throwaway Listenarr with its library on local storage and real Audible searches; the checks read the API and the files on disk. The sample library is generated by m4bard/listenarr-testdata (commit bd59f6f, seed 1): five real public-domain books with verified ASINs, as silent one-second files carrying genuine tags (two of them in 40 chapters), in an author/title layout. The instance has French settings. A seed runs a real Library Import and is snapshotted (database, library, import folder); every other scenario restores it first. All five pass on this branch (full run about 2.5 min). Every repair accepts the question "Repair n books?" first. An earlier run found one bug, fixed here: the "Leave while books are repaired?" confirmation opened under the Repair dialog, unseen and unclickable (the shared confirm dialog's overlay is now above the dialog that asks it, ConfirmDialog.vue).

Scenario What it checks Result
00 Seed: Library Import Scan 5 folders, Start Matching (only Vingt mille lieues sous les mers is sold in France: matched to its French record), Find Match points it by mistake at the English Twenty Thousand Leagues Under the Sea (US) and finds the three US books in the US, import 4 books with Move; 82 audio files in the library, A Princess of Mars left in the import folder Pass, 30 s
10 Repair from the book page Vingt mille lieues searched in France, its French record picked; the question "Repair 1 book?" canceled, then dismissed with Escape: step 1 as it was; then accepted, repaired, organized as step 3; primary ASIN B0F48KS3BX/fr, the French title, no series left (the English record's Captain Nemo #1), its 40 files in the new folder (not under Captain Nemo), no audio left in the old one Pass, 50 s
11 Repair several from the library Dr Jekyll and Mr Hyde left out ("3 skipped" → "1 skipped"), Pride and Prejudice on its Current record, Vingt mille lieues on its French record; Organize 1 with Pride and Prejudice unticked, Back to step 2 as it was left, Back to step 1 loaded afresh; the two others unchanged Pass, 8 s
12 Leaving during repairs Browser Back during "Repair 3" asks, the confirmation on top; Stay: all three done, still on the page; Back once over leaves without asking; again with Leave: the book still waiting is not repaired Pass, 38 s
13 Find Match on a matched folder A Princess of Mars matched in the US, Find Match opened again: the scan's ASIN in the ASIN field, France, the scan's search, the US match only on Current Pass, 4 s
Every step, its expected result, and what the runs showed

00 Seed

  1. Scan "Import local" → "Last scanned … 5 unmatched".
  2. Start Matching → Vingt mille lieues on its French record (B0F48KS3BX); the US books "No match found" (not sold in France).
  3. Find Match on Vingt mille lieues: region US, ASIN emptied, title Twenty Thousand Leagues Under the Sea, author Jules Verne, pick B01FKWL15A, Use this match: the wrong record, on purpose.
  4. Find Match on Robinson Crusoe, Dr Jekyll and Mr Hyde and Pride and Prejudice: region US (searches the scan's ASIN by itself), pick the result with that ASIN, Use this match.
  5. Import 4 Books with Move into the library folder → the library holds 4 books.
  6. API and disk → the four ASINs; 82 audio files in the library (40 + 40 + 1 + 1); 1 left in the import folder.

10 Repair from the book page

  1. Repair from Vingt mille lieues' page → the card opens; the book is B01FKWL15A/us.
  2. ASIN cleared, title Vingt mille lieues sous les mers, author Jules Verne, region France, pick B0F48KS3BX → "Repair 1".
  3. Repair 1 → "Repair 1 book? … This can't be undone."; Cancel → step 1, "Repair 1", nothing changed; Repair 1 again, Escape → the same.
  4. Repair 1, accepted → step 2 ends, the book done.
  5. Organize 1 (step 3), Done.
  6. API and disk → B0F48KS3BX/fr; the French title; no series (it was Captain Nemo Basic Functionality #1); a new folder, not under Captain Nemo; its 40 files exist; no audio in the old folder.

11 Repair several from the library

  1. Tick Dr Jekyll and Mr Hyde, Pride and Prejudice, Vingt mille lieues; Repair Selected (3) → three cards, "3 skipped".
  2. Pride and Prejudice: Current picked; Vingt mille lieues: its French record picked → "Repair 2", "1 skipped".
  3. Repair 2 → both done; Dr Jekyll and Mr Hyde not on step 2.
  4. Untick Pride and Prejudice, Organize 1, Back → step 2 as it was left, "Organize 1".
  5. Back → step 1 afresh: Vingt mille lieues' Current is B0F48KS3BX, "Repair 0".
  6. API → B0F48KS3BX/fr; the two others unchanged.

12 Leaving during repairs

  1. Library Import, then Audiobooks by the sidebar; Pride and Prejudice and Robinson Crusoe on Current, Vingt mille lieues on its French record, last → "Repair 3".
  2. Repair 3, browser Back → "Leave while books are repaired?", the element at its centre is its own, it says the waiting ones are not repaired.
  3. Stay → all three done; still on Audiobooks; B0F48KS3BX/fr.
  4. Back once over → Library Import, nothing asked.
  5. Same three, Vingt mille lieues pointed back at the English record; Repair 3, Back, Leave → Library Import.
  6. 20 s later → still B0F48KS3BX/fr: its repair, waiting when Leave was chosen, never ran.

13 Find Match on a matched folder

  1. Scan what is left, Start Matching → "1 unmatched", matching ends.
  2. Find Match on A Princess of Mars: region US (searches the scan by itself), pick B008DFUGCQ, Use this match → its row shows it.
  3. Find Match again → the scan's ASIN (B008DFUGCQ) in the ASIN field, region France, the scan's search, the US match only on Current, results "in France (FR)".

What the runs showed

  • The bug above: step 2 of scenario 12 failed before the fix (the confirmation was under the Repair dialog), passed after it.
  • Not covered here: closing the tab and two tabs at once (the known limit above), and what the unit specs and the captures below cover.

How it looks

Every interaction, step by step: the real dialogs (this branch's components and styles) in a browser over a simulated API, so the timings and failures show. Every repair answers "Repair n books?" first. Click a group to open it.

Step 1, matching (13)

A title search revealing its results; a record picked (ticked, New with the changes in green, Current with the replaced parts in orange, bar green), clicked again to unpick it, another one picked.
s1-01-search-pick-unpick

Typing only prepares: Enter or Search runs the search, Search becomes Retry, a changed field makes it Search again; an erase button.
s1-02-fields-enter-search-retry

A region change searches there; back to a region already searched: Cached results; Retry asks Audible again.
s1-03-region-and-cached-results

A filled ASIN searched on its own: no record in the new region (orange), a typed ASIN found and picked, an ASIN that isn't one.
s1-04-asin-lookup

A slow search (3 s): the bar breathing blue, the spinner over the list; Cancel search, then Retry.
s1-05-slow-search-cancel-retry

A failed request: the empty list with the reason in red in its middle; Retry.
s1-06-failed-search-retry

Revert: the fields back as they were, the pick dropped.
s1-07-revert

One search at a time: the other book waits, greyed out; nothing moves when the first answers.
s1-08-one-search-at-a-time

A matched book unticked (skipped), Clear, Select All.
s1-09-selection

The legend centred on the dialog, moved aside, then above the buttons.
s1-10-footer-narrowing

Cancel closes the dialog.
s1-11-cancel-dialog

Three books selected: one deleted since (its card says so, nothing to load again), one whose load timed out (Load again reads it); the others stay repairable.
s1-12-books-not-loaded

A record without an ASIN (Audible has nothing, the server adds one from OpenLibrary): greyed, tagged "No ASIN", and it can't be picked: a repair needs a record a metadata provider can read again.
s1-13-record-without-asin

Step 2, the repair process (15)

Repair asks first, since it can't be undone: Cancel, then Escape, both leave step 1 as it was; asked again and accepted, the repair runs.
s2-13-repair-asks-first

One book, a slow repair: step 2 at once, the dialog keeping step 1's height.
s2-01-one-book-slow-repair

A fast repair.
s2-02-one-book-fast-repair

Three books, one repair at a time; the bottom bar filling.
s2-03-three-books

A failed repair and one done in part: the bottom bar orange, the warning.
s2-04-failed-and-in-part

Cancel during the repair: the running one ends, the others are canceled.
s2-05-cancel-during-repair

Show details / Hide details: Old with the replaced parts in orange, New with the changes in green.
s2-06-details

What to organize: a book unticked, Clear, Select All.
s2-07-organize-selection

Step 2's footer as the dialog narrows: the warning's place going above the buttons.
s2-08-footer-narrowing

Back, once the repairs are over: step 1 loaded afresh, each book as it was repaired.
s2-09-back-to-matching

Retry on a failed repair: that book repaired again, the warning gone once it works.
s2-10-retry

Leaving the page during the repairs (browser Back, shown by the label): the confirmation on top of the dialog, the repairs held while it asks (the running book ends, the next one waits); Stay, and they go on to the end.
s2-11-leave-stay

The same, then Leave: the book being repaired ends, the ones still waiting are not repaired, and the page changes.
s2-12-leave-leave

A rescan refused for coming too soon (the server's 15 s cooldown): the row counts down, then the book is repaired.
s2-14-rescan-cooldown

The rescan read another record than the picked one (the server fell back to the book's other ASIN): the row ends in orange, naming it, and the book isn't ticked for Organize.
s2-15-another-record-read

Step 3, Organize (2)

Organize 2 opens the Organize dialog as Repair's step 3, then organizes.
s3-01-organize

Back from Organize: step 2 as it was left.
s3-02-organize-back

Find Match (Library Import) (12)

Opens searching the folder's title and author; pick, unpick, pick another, then Use this match.
f-01-open-search-pick-use

Typing only prepares; Enter searches; an erased author, Search; nothing changed, Retry.
f-02-fields-enter-search-retry

A region change searches there; back to a region already searched: Cached results; Retry asks again.
f-03-region-and-cached-results

An ASIN in the folder's files: in the ASIN field and looked up on open, its record offered (not picked), then used.
f-04-asin-from-files

A typed ASIN looked up with Enter and offered; one that isn't an ASIN said without a request.
f-05-typed-asin-and-invalid

The search on open is slow: Cancel search (orange), then Retry, answered.
f-06-slow-search-cancel-retry

The search on open fails: the reason in red; Retry, then pick and use.
f-07-failed-search-retry

Revert: the fields back to what the files said, the pick dropped.
f-08-revert

Already matched (the French Vol. 7): it opens with the same search as any folder, from its scan, its match only shown as Current; in France its own record is tagged Current, nothing to use; back to the US (cached), the English one used.
f-09-already-matched

A record picked, then a new search: Use this match waits for it, and the pick is dropped.
f-10-new-search-drops-pick

Closed during the search on open: the dialog goes, nothing sent back.
f-11-close-during-search

A book Audible doesn't have: the search adds an OpenLibrary record, without an ASIN; Find Match can pick it and use it (Library Import adds the book from it), as canary did.
f-12-record-without-asin

🤖 Generated with Claude Code

@Yoldark34 Yoldark34 changed the title WIP: Repair books, an Issues page, and one backend book search (on top of #1043) [Epic #1080] Repair: point books at the right Audible record, from the book page and the library Oct 21, 2026
Yoldark34 added a commit to Yoldark34/Listenarr that referenced this pull request Oct 21, 2026
@Yoldark34 Yoldark34 changed the title [Epic #1080] Repair: point books at the right Audible record, from the book page and the library [Step][#1080] Repair: point books at the right Audible record, from the book page and the library Oct 21, 2026
Yoldark34 added a commit to Yoldark34/Listenarr that referenced this pull request Oct 21, 2026
… Match

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Yoldark34 added a commit to Yoldark34/Listenarr that referenced this pull request Oct 22, 2026
@Yoldark34
Yoldark34 force-pushed the wip/repair-books branch 2 times, most recently from 181d58f to 9eca254 Compare October 23, 2026 12:52
Yoldark34 added a commit to Yoldark34/Listenarr that referenced this pull request Oct 23, 2026
Yoldark34 added a commit to Yoldark34/Listenarr that referenced this pull request Oct 23, 2026
Yoldark34 added a commit to Yoldark34/Listenarr that referenced this pull request Oct 23, 2026
@Yoldark34
Yoldark34 force-pushed the wip/repair-books branch 4 times, most recently from 5b3683b to c8f4859 Compare October 28, 2026 00:48
Yoldark34 added a commit to Yoldark34/Listenarr that referenced this pull request Oct 28, 2026
…ired (Stay, Leave)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Yoldark34 added a commit to Yoldark34/Listenarr that referenced this pull request Oct 28, 2026
…ile it asks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Yoldark34 and others added 14 commits October 29, 2026 20:20
Both overlays sit on body at z-index 3000, and the global confirm dialog is mounted first (App.vue),
so a confirmation asked from inside another dialog (leaving the page while books are repaired) came
under it, unseen and unclickable. Found by the E2E run on the test instance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Leaving the page during the repairs asked first, but the queue went on meanwhile: a user slow to
answer saw more books repaired than the question said. Now the book being repaired ends and the
next one waits for the answer; Stay goes on with them, Leave cancels the ones still waiting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ver says

The server rescans a book at most once every 15 s and 5 times in 10 minutes,
answering 429 with how long to wait. The repair waited a fixed 3 s and asked
again once, so a book repaired again within the cooldown always failed.
Now it waits the time the server gives (15 s when it gives none), its card
counting down, then asks again; a longer wait (the 10 minutes' limit) fails
at once, saying when to retry. Cancel also stops the book waiting there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The library keeps one Repair dialog. Closed while its books loaded, then
opened for other books, it ran two loads: the one answering last filled the
cards (possibly the previous books), and the one answering first ended the
loading. Each load now takes a number and only the latest one touches the
dialog; it reads the books asked when it starts, and closing the dialog
leaves a load still waiting out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ays so

The server reads the record from the book's other identifiers (its other
ASINs, then ISBNs) when the picked one fails, and answers with the ASIN and
region it used. The repair said Repaired whatever they were. Now, when they
aren't the picked record's, the book ends in orange with that record and
"check the book", and isn't ticked for Organize. Three tests that meant a
rescan only now pick the book's current record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rescan only writes what the new record has, so a repair kept every value
the new record lacks from the wrong one: its series and number, subtitle,
narrators, genres... and Organize, right after, filed the book under the old
series. When the ASIN or its region changes, those details are now emptied
between saving the new ASIN and the rescan, which then fills what the new
record has; the title, authors, cover and runtime are always read again. If
they can't be emptied, the repair stops there, done in part.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A repair can't be undone, so Repair now asks first ("Repair n books?"):
each book takes its new record as a whole, its details replaced and what
the record lacks emptied, changes made by hand lost, the files, folder and
settings kept. Cancel goes back to step 1 as it was; Retry doesn't ask again.

The details are emptied on every repair, a rescan of the same record too,
so the book is exactly its record. When a repair stops part way, its row
says what was already saved: the new ASIN, the emptied details, or both.

Escape on a confirmation shown over the dialog (this one, or leaving the
page) answers it only: every open dialog heard it, and Repair Books closed
too, losing the picks or step 2's results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dialog loaded every selected book at once, and one failure (a book
deleted since it was selected, a request timing out) left it showing only
the error, none of the books repairable. Books now load one at a time, like
the repairs. One that fails gets its own card in the error state, saying
why, which can't be ticked; Load again reads it anew, except a book deleted
since it was selected. When none can be loaded, the dialog says why as
before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t run paused

The leave question pauses the repairs; Stay resumes them, Leave cancels the
waiting ones, but only while repairs ran. Chosen after the last one ended
under the question, Leave kept the pause, and if the page then stayed (its
navigation failed: the page's code couldn't load after an update), the next
Repair left every book waiting until Cancel. Cancel now always clears the
pause, and a new run starts without one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ASIN field had a 10-character limit, which a browser applies to a paste
before the trim: " B0BK9W82PQ", copied with a space before it, became
"B0BK9W82P" and Search said it wasn't an ASIN. The limit is gone, as in Add
New's search box; Search still checks the ASIN's format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ck or not

After the rescan, the book was read back to show it, and a failed read
(a timeout) was reported as a failed rescan, in orange, the book left out of
Organize, although the server had it repaired. Now the book the rescan
answers with is used when the answer holds it (Listenarrs#1128), with no request; until
then it is read back, and a failed read only leaves the row showing the picked
record, "Repaired …; the book couldn't be read back to show it". The row
shows the subtitle and authors the server has, none kept from the old record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the folder's scan found an ASIN, Find Match put it in the Title field,
the ASIN field empty: searched as a title, it said "Nothing found: no book
for this title and author" where the region doesn't sell it, without the
ASIN's state, and the scan's title and author were lost. It now opens with
the ASIN in its field, the scan's title and author ready, and the search on
opening follows the card's priority, as Search does: "No record for … in
Audible France". Start Matching already searched it as an ASIN. A value the
scan took for an ASIN but isn't one stays a title.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…behind it

Organize, Repair's step 3, waited for the whole library (or the book) to be
read again before opening, with no dialog meanwhile: a blank moment that grows
with the library. Organize reads its previews from the server, so it now opens
at once and the page behind is refreshed meanwhile; the book page does it
quietly, keeping the book shown instead of its loading state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Yoldark34 and others added 5 commits October 29, 2026 20:54
… and dates

An ASIN is looked up through the metadata endpoint, whose answer has the
record's series, genres and release date, but the search result built from it
kept none of them, unlike a title search's results. Find Match uses Add New's
search, and Library Import adds a book from the picked result: a book matched
by its ASIN was imported without its series, number, genres or year, and moved
into a folder without its series. The result now carries them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… fallback

The server stores the ASIN a repair saves as Manual, and a repair kept every
Manual identifier: a book repaired twice kept the first repair's ASIN, which
the rescan reads when the new one fails, and each repair added another. The
ASIN the book is repaired away from is now dropped, whatever its source; other
Manual identifiers (an ASIN added for another region, an ISBN typed by hand)
stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…repairing

The repairs and a Retry set the dialog repairing, then back, with nothing in
between to catch an error outside a book's own repair (a broken result while
the planned records are built, the dialog's measures). Such an error vanished
and left the dialog repairing for good: it couldn't be closed, Cancel stayed
Canceling, and the page kept asking before being left. The run now always
ends; the books not repaired end failed, "Repair stopped: …", shown on step 2
and retryable, and the error is logged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t asks

"Repair n books?" is the app's confirmation, not the dialog's: left open while
the page changed (browser Back, a redirect to the login), it stayed over the
next page, and accepting it then ran the repairs with no dialog, Cancel or
guard. The question now closes, as Cancel, when the dialog goes, and an answer
given after the dialog has gone starts nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ooldown finish

The leave question promises the book being repaired ends its repair, but Leave
canceled like Cancel, which also stops a book waiting for the server's rescan
cooldown: that book was left with its new ASIN and its details emptied, unseen.
Only Cancel stops the wait now, its row saying so with Retry; Leave, or the
dialog going, lets the book finish its rescan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Yoldark34 and others added 4 commits October 29, 2026 23:39
…an't

When Audible has nothing for a search, the server adds records from another
source (OpenLibrary) without an ASIN. Canary's Find Match could match a folder
to one and Library Import add the book from it; the shared card ignored the
click, so a book Audible doesn't sell could no longer be imported. Find Match
picks and uses such a record again (records are told apart by their id there).
Repair needs a record a metadata provider can read again: such a result is
greyed, tagged "No ASIN", with the reason, and can't be picked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cards of books that couldn't be loaded shared one "Loading…" flag: a
second Load again took it from the first card, and the first answer cleared
it from the second, whose button came back on while its request still ran, so
a click sent the same request twice. Each book now has its own, and Load again
on a book already loading does nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spec mounted each dialog on the page and most tests never unmounted it:
dozens stayed alive, still hearing Escape, able to warn before the tab
closes, and calling the shared mocks during later tests. Every mounted dialog
is now unmounted after its test (the composable spec stops its scopes too);
only the tests that make the dialog go on purpose still unmount it themselves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A result without an ASIN in Repair had its "No ASIN" tag and couldn't be
picked, but looked like any other result. It is now greyed, with the
not-allowed cursor and no hover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Yoldark34 added a commit to Yoldark34/Listenarr that referenced this pull request Oct 29, 2026
… before repairing, books not loaded, the rescan cooldown, another record read, records without an ASIN, Find Match)
@Yoldark34
Yoldark34 marked this pull request as ready for review October 29, 2026 23:25
@Yoldark34
Yoldark34 requested a review from a team October 29, 2026 23:25
…s fit in

The room kept for the results was a fixed 320 px: with the rest of the
dialog, Repair with one book scrolled on a 1080 px window (218 px at 900), and
Find Match on a 900 px one. A card alone in its dialog now keeps the room left
by the rest, at least two results high (the list scrolls inside past that),
measured when it shows and when the window is resized, before any search
answers. Repair with one book has no Selection block either: the book's own
checkbox says it all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Yoldark34 added a commit to Yoldark34/Listenarr that referenced this pull request Oct 29, 2026
…room its results fit in; one book without Selection
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.

1 participant