Skip to content

v1.30.1

Latest

Choose a tag to compare

@github-actions github-actions released this 11 Aug 02:57
· 2 commits to main since this release
df92a02

The onboarding half of this release is about the same blind spot: until now the
only evidence that first-run setup had worked was a download turning up hours
later, so a mis-wired install looked exactly like a working one. There is now a
progress checklist that ticks through indexer → download client → author → grab
→ import, warnings on the three ways a first run fails silently, and an update
badge so you find out a release exists without going looking.

The fixes are mostly a search-and-metadata sweep, and several of them are the
same shape: a feature that was fully built except for the one call that would
have made it work. The ISBN ranking bonus could never fire because nothing put
an ISBN in the search criteria. A Hardcover import list's quality profile was
saved, displayed, and never read. Reorganize's copy fallback would walk into the
directory it was creating and fill the disk. And the Hardcover client never
asked which role a credit was for, so audiobooks filed themselves under their
narrator and put themselves back there on every refresh.

Added

  • Setup progress checklist on the Authors page — indexer → download client → author → grab → import, ticking off as each happens and disappearing for good once a book has imported. This is the "your setup works" confirmation the app never had: until now the only evidence that first-run setup succeeded was a download showing up hours later, so a mis-wired install looked identical to a working one. Backed by a new GET /api/v1/system/setup-state; it replaces the getting-started card on that page (the checklist says everything the card did, plus what comes next).
  • In-app update badge — the header version link (and Settings → About) now turns into an amber v1.28.0 → v1.30.0 badge when a newer release exists, linking to the release. The telemetry ping already carried the latest published version on every response; the app now actually uses it instead of discarding it. Installs with telemetry disabled see no badge (the app has no other way to learn a release exists), and dev/sha builds never compare. Telemetry fleet data shows why this matters: docker installs are 81% current within a week of a release, while binary installs are 35% — the gap is discovery, not willingness.
  • "Updated to vX — see what's new" toast after an upgrade, closing the loop the update badge opens: the badge tells you an update exists, this confirms it landed and links to the release notes. Shows once per version, never on a first-ever load (a fresh install has nothing to catch up on), and never for dev/sha builds.
  • Setup-funnel telemetry (anonymous, opt-out with the rest of telemetry) — the daily ping now includes whole-day offsets from install to first indexer, first download client, first author, first grab, and first import (e.g. setup_indexer_day: 0 = configured the same day). Integers only, never timestamps. Fleet data showed installs that reach "indexer + download client" retain 66% at 7 days vs 16% for those that don't — these fields make the stall points in that funnel visible so onboarding changes can be measured. Documented at getbindery.dev/telemetry-fields.

Fixed

  • German titles with umlauts find their releases (#1610) — indexer queries were sent with literal ä/ö/ü/ß while Usenet release names use the ASCII convention (Phönix vs. Phoenix), so every umlaut-containing title returned (near-)zero results and never auto-grabbed. Book-search queries now transliterate German umlauts (ä→ae, ö→oe, ü→ue, ß→ss) before being sent; other Latin diacritics (é, ñ, ç) and non-Latin scripts are left unchanged, and free-text searches are untouched. When the transliterated query finds nothing at all, the search retries once with the original umlaut spelling to catch the rare releases that keep it.
  • Hardcover no longer files books under their narrator or translator (#1733) — the client never asked Hardcover which role a credit was for, so it took whichever contribution came back first and matched author lookups on name alone. Books landed under an audiobook narrator (Will Wight's Cradle volumes showing Travis Baldree as the author), and because the per-author refresh matched every contribution role by name, correcting a book by hand only held until the next "Refresh All Metadata" put it back. Every book query now requests the contribution role, the author lookup only matches author-role credits, and metadata resolves to the actual author. Books whose only credit is an editor or illustrator keep that credit rather than losing an author entirely.
  • Reorganize no longer fills the disk when a book's new location sits inside its old one (#1809) — a layout change that computes a destination nested under the source (a series folder named after the book, so /library/Author/Title moves to /library/Author/Title/Title, or a flat author folder that becomes /library/Author/Book/) cannot be renamed by the kernel, so it fell through to the copy-based move, which then read the source while the destination grew inside it: the copy descended into its own output and nested directories forever until the drive was full and the container had to be killed. Directory moves, copies and hardlink placements now check containment up front and refuse the move with an error naming both paths, and the reorganize preview flags it before anything is applied. Source and destination are resolved through symlinks and compared on path component boundaries, so a genuine sibling like Book Two next to Book still moves normally.
  • "Search all wanted" no longer floods an indexer with duplicate and empty-term queries (#1814) — a 26-book author sent roughly 294 searches to one indexer over 15 minutes, and every search after the first minute failed with context deadline exceeded so nothing was grabbed. Three things multiplied: a book whose title normalised to nothing (a blank title, or a row whose title is only an edition qualifier like (Unabridged)) still ran the full four-tier query cascade and put q= on the wire with nothing after it; two catalogue rows for the same work produced byte-identical queries that were both sent, and a sweep overlapping an earlier one repeated every query that sweep had already made; and the bulk fan-out ran at twice the concurrency of every other search fan-out in Bindery. Unsearchable titles are now skipped outright, identical queries to one indexer are collapsed for 90 seconds, and the bulk fan-out runs at the same bound as the per-author auto-search. A repeat or overlapping sweep is now close to free.
  • Releases that name a book's ISBN now actually win the search (#1724) — the ranker has always carried a large exact-match bonus for a release whose title contains the book's ISBN, but nothing ever put an ISBN into the search criteria, so the bonus could not fire on a single search Bindery has ever run. Both search paths (interactive search and the scheduler's auto-grab) built their criteria with title, author, year and ASIN and silently left the ISBN empty, which is why an ISBN-tagged release ranked no better than an untagged one and auto-grab could pick the wrong edition when the right one was sitting there labelled. Both paths now read the book's editions and populate the criteria, converting an edition recorded only as an ISBN-10 into its ISBN-13 form so it matches — a release name can only ever carry the ISBN-13.
  • One-word book titles no longer grab a longer book that merely starts with that word (#1731) — a book titled Treasure auto-grabbed and silently imported Clive Cussler - Dirk Pitt Universe Bk 29 - The Treasure of Khan, a different book by the same author, which landed under the right book record with no error. The single-keyword match path only asked for the word at a word boundary plus the author somewhere in the release, and the author corroboration added for embedded titles cannot separate two books by the same author. The matcher now looks to the right of the matched word as well as the left, and rejects a release where the word runs on through a connective (of, and, in, to, …) into a further title word. Trailing years, formats, bitrate and edition markers, series labels, language tags and the author's own name are all still accepted, as are subtitles opening with an article (Treasure - A Dirk Pitt Novel), so correctly named releases keep matching.
  • Series volumes sharing a base title no longer collapse into one book (#1785) — the canonical dedup key strips a ": subtitle" tail, so a series titled Series: Volume (e.g. Tao Wong's A Thousand Li, the Gears of War novels, omnibus collections) mapped every volume onto a single key. On an Audiobookshelf import that merged the extra volumes (a 902-item library created only ~824 books, the rest linked or queued for review); on a manual add by provider id it left the requested id bound to nothing, so the request failed forever with "book not found after author sync — try again shortly". The dedup match is now series aware: a candidate that shares a series but sits at a different sequence number is treated as a distinct work, so each volume gets its own row. Same-sequence editions (an audiobook that drops the subtitle) still merge as before.
  • Hardcover list sync no longer stalls on large lists (#1694) — syncing a list issued one fully-paginated GraphQL edition query per newly imported book, so a first sync of a big shelf ran out of the request's time budget after a handful of books and reported success anyway. The audiobook ASIN, language, and media type those queries were after now arrive inline on the list response itself (via the default-edition relations — the books type has no language field of its own). Audnex audiobook enrichment still runs for books whose ASIN arrived inline, without any edition fetch. Measured on a real 1,660-book shelf against v1.30.0: the scheduled (deadline-free) sync now completes the full list; the manual "Sync now" path imports ~4.7× more books per request (519 vs 111) with ~7× the language coverage.
  • A Hardcover import list's quality profile now applies to the authors it creates (#1781) — the per-list quality profile picker wrote its choice to the list row, but the sync never read it back: ensureAuthor built each new author by hand and left qualityProfileId null, so ResolveAuthorQualityProfile returned "no profile" and both the scheduled grab and the interactive search let every format through for that author's books. A list configured as audiobooks-only would happily grab epubs. The syncer now stamps the list's configured profile onto the authors it creates, so the filter is enforced from the first sync. Existing authors keep the profile they already have — a re-sync never overwrites a choice made on the author itself — and a list with no profile configured still leaves the author unfiltered rather than guessing at a default.
  • API-key clients can mutate again when auth.mode=local-only (#1849) — a POST/PUT/DELETE carrying a valid X-Api-Key from an address local-only mode trusts (RFC1918 or loopback) came back 403 {"error":"forbidden"}, even though the same request was being treated as an authenticated admin. Integrations like Harpoon and *arr-style callers had to also send X-Requested-With: bindery-ui — the web UI's own private header — to get through. The local-only bypass ran ahead of the API-key check and returned early, so the request was never marked as API-key-authenticated and the CSRF header guard, which exempts API-key clients, did not exempt it. The key check now runs first: both branches grant admin, so the only thing that changes for a trusted-local caller is that a verified key earns its documented exemption. A request with no key, or a wrong one, still falls through to the local-only bypass and still needs the header on a mutation, so browser CSRF protection on the LAN is unchanged.
  • Calibre imports no longer log a constraint failure for every series link, and rollback can now unwind them (#1635) — the run-tracking tables shipped with entity_type pinned to author/book/edition, but series persistence records book-to-series memberships as series_link, so every linked book produced a CHECK constraint failed warning and left no provenance behind. Rolling back that import then deleted the books and authors while quietly leaving the series memberships in place. A new migration widens the constraint on both tracking tables (rebuilt in place, existing rows and indexes preserved), and rollback now unwinds a series link explicitly, before the book it belongs to. Only memberships the run actually created are claimed, so a link you made yourself, or one an earlier import created, survives. Part 2 of the report — book_files rows never being written during a Calibre import — is still open.
  • Deleting an author with "delete files" now removes every tracked file (#1811) — the sweep enumerated only the legacy file_path column, so a book's ebook/audiobook files tracked in book_files (multi-format books, audiobook folders) and any excluded book's files were left orphaned on disk. It now walks book_files per book (falling back to the per-format/legacy columns) and includes excluded books, matching the single-book delete path.
  • Retrying a failed import clears the old error instead of showing it forever (#1633) — retry-import reset the retry count and the status but left error_message untouched, so a queue row that recovered kept rendering Error: import retry limit reached (3 attempts) next to an import that had demonstrably succeeded. Only the transition into imported cleared the field, and external-mode hand-offs settle in importExternal instead — a deliberately non-terminal state whose rows stay in the queue permanently, so the contradiction never aged out and made a working import look broken. The retry now clears error_message in the same statement that re-arms the row, so a retried item starts clean.
  • Three silent first-run failure modes now warn in the UI instead of living only in QUICKSTART.md: (1) an enabled Torznab/Newznab indexer with no matching-protocol download client shows an amber warning on the Indexers/Clients settings tabs (searches would find releases nothing could download); (2) turning on auto-search when the pipeline is incomplete warns right in the Add Author dialog (the background search's failure was invisible); (3) a failed download-client connection test against localhost explains the Docker loopback trap and what to use instead.
  • Half-configured installs now get setup guidance. The getting-started card required indexers AND download clients to BOTH be missing, so a user who had configured one of the two — the state where searches or grabs fail silently — saw no guidance at all. It now names the one missing step ("…every grab will fail") and links only to it. Guidance also no longer hides inside the Authors/Books empty states: a dismissible banner in the app shell shows while the pipeline is incomplete, so library-importers (whose pages are never empty) finally see it too.
  • The search debug panel no longer reports mediaType: ebook for a media_type=both book (#1636) — a dual-format book is searched once per format so each leg uses its own category tree (7xxx for ebooks, 3xxx for audiobooks), and the two debug payloads are merged into the one panel the UI shows. The merge kept the ebook leg's query summary, so a search that had genuinely queried both trees — with the 3xxx indexer rows visible in the same panel — labelled itself "ebook", which reads as a media-type misconfiguration and sends you hunting for a bug that isn't there. The merged summary now reports the book's own media type, while the per-indexer rows below it continue to show each format's categories separately.
  • Helm chart appVersion no longer drifts from the deployed tag — it had been frozen at 1.22.3 while values.yaml advanced to 1.30.0, because the release pipeline only bumped the latter. The prod-deploy step now updates both, and appVersion is corrected to 1.30.0.
  • The setup-funnel cohort is gated on the release that actually reports it — the gate was pinned to 1.31.0 on the assumption the funnel fields would ship in a minor release, and would have excluded every install that reports them, leaving the /stats setup-funnel section permanently empty. It now gates on 1.30.1, and accepts a version with or without a leading v: the Docker image reports v1.30.1 (CI builds it from git describe) while the GoReleaser binaries report the bare form, and the telemetry client sends whichever it was built with — so a bare-form-only gate would have dropped every Docker install out of the cohort, which is indistinguishable from an install that stalled before its first milestone.