The Grimmory integration goes from a settings tab nothing read to a working
push pipeline, and the UI can now be embedded in a dashboard iframe when an
operator explicitly opts in. A new per-author "Monitor newly discovered
books" policy defuses the refresh-mass-monitors-the-back-catalogue trap.
Three community bug reports filed this week are fixed in the same cut: books
silently stranded under the wrong author, the author-page filters mishandling
dual-format books, and unreadable NZB grab failures. Repo-side, the CI
pipeline gains AI triage/review bots and a hardening pass on the fork-facing
workflows.
Added
- Grimmory push pipeline — BookDrop upload on import plus bulk sync
(#1392, closes #826) — the Grimmory integration was configuration-only: a
Ping()client and a Settings toggle no code ever read. It now works end to
end. A newinternal/grimmoryclient does JWT auth against Grimmory's API
(login → access/refresh pair, rotation, one 401 retry after re-auth; a set
api_keyis honoured as a static Bearer token and bypasses login), and
streams a multipart upload toPOST /api/v1/files/upload/bookdropon a
dedicated 5-minute timeout. APusheris hooked into the importer alongside
the Calibre/CWA handoffs — settings are read live per push (no restart) and
pushes are best-effort by contract, so a Grimmory failure never fails the
import. BookDrop has no server-side dedup, so idempotency is Bindery's:
migration 059 adds agrimmory_pushestable keyed by file path, consulted
on every push. Bulk sync (grimmory.Syncer, admin-only
POST /grimmory/sync+GET /grimmory/sync/status) mirrors the Calibre
single-job pattern (409 on concurrent start, polled progress, capped error
list). Settings → Grimmory gains username/password fields, a real
Test Connection login check, a "Push all to Grimmory" button with live
progress, and the experimental banner is gone. Ebook files only for now —
BookDrop takes one file per upload, which multi-part audiobook folders don't
reduce to. - Opt-in iframe embedding via
BINDERY_FRAME_ANCESTORS(#1367) — the UI
could not be embedded in an<iframe>becauseSecurityHeadershard-coded
X-Frame-Options: DENYand CSPframe-ancestors 'none', blocking use inside
dashboards like Organizr. That clickjacking lockdown stays the default; an
operator can now setBINDERY_FRAME_ANCESTORSto a CSPframe-ancestors
source list ('self'for same-origin, or a specific origin such as
https://organizr.example.com) to allow framing per trusted origin. When
set,X-Frame-Optionsis dropped, since it can't express an origin allowlist
andDENY/SAMEORIGINwould override the more expressive CSP directive.
Documented indocs/DEPLOYMENT.mdandcharts/bindery/values.yaml. - Per-author "Monitor newly discovered books" setting (#1348) — "Refresh
Metadata" shares the add code path, so on a default-config author (monitor
modeall) a refresh that discovered the provider's full back-catalogue
created every work monitored + Wanted, queueing a search storm the user
never asked for. Each author now has a Monitor New Items policy in the edit
modal: Follow monitor mode (default, previous behaviour) or Add as
unmonitored, which applies to works discovered after the initial sync —
the add flow and migrations still honour the monitor mode, and a genuinely
wanted back-catalogue is one bulk-monitor away. Import-created authors
(Calibre, Audiobookshelf) default to Add as unmonitored: they start
with a partial catalogue, which made the first refresh after an import the
classic detonation point.
Fixed
- Author sync no longer strands books under the wrong author (#1405) — a
work fetched during an author's sync could match an existing row created
under a different author record (a duplicate OpenLibrary author key, a
Calibre shell author, or an earlier book-level add). The sync refreshed that
row's ratings forever but never re-linked it, and since the author page
filters byauthor_idthe book was permanently invisible under the real
author — the reported case being Elantris missing from Brandon Sanderson
despite being fetched on every sync. The sync now re-links such rows to the
author being synced, using the provider's credited-author list as the
safety check: a genuinely co-authored work (credited to both authors, e.g.
the Wheel of Time books Sanderson finished for Robert Jordan) stays with
its current owner so it can't ping-pong between authors on alternating
syncs, and when authorship can't be determined the row is left untouched. - Author-page filters now respect the selected media type for dual-format
books (#1406) — a book wanted as Both was invisible under the
Type: Ebook / Type: Audiobook chips (the filter comparedmediaType
exactly), and the Status chips judged the combined status, so a Both book
whose ebook was already imported never showed under Type: Ebook +
Status: Imported while its audiobook was still wanted. Both books now match
either type chip, and with a type selected the status filter judges that
format's own state (its file on disk → imported), so each side of a
dual-format book filters correctly. - NZB grab failures now explain themselves — including the NZBFinder
"error 203" case (#1404) — when an indexer refuses the NZB download, the
error now surfaces the parsed newznab code and description instead of raw
XML, and when the grab was redirected off its original host (Prowlarr's
per-indexer Redirect setting handing the fetch to an app-whitelisting
indexer that rejects Bindery's identity) the error names both hosts and
points at the Prowlarr setting to disable. Applies to both SABnzbd and
NZBGet grabs; a matching entry was added to the troubleshooting guide.
CI
- AI triage, PR review, and nightly backlog-sweep bots (#1222) — new
ai-triage, PR-review, andai-sweepworkflows automate issue triage and
first-pass PR review. The same change hardens the fork-facing notify
workflows: thepull_request_targetpath no longer interpolates
github.event.*inline (script-injection vector), permissions are cut to
the minimum each job needs, and the ESLint security scan is pinned in the
lockfile so the SARIF step actually runs instead of silently no-opping.