Detail page:
- collapse Chapters section behind header toggle (73-chapter pages no
longer push content below the fold)
- square cover frames throughout (audiobook covers are Audible-style 1:1,
not 2:3 book portrait)
- narrator picker dropdown when multiple narrations of the same book exist
- clickable genre badges (route to /audiobooks?genre=X)
- reordered so credits/rails sit above the chapter list
- Play-from-Start button forces remount via playToken counter (was a
no-op when player was already at position 0)
- regression test for the Play-from-Start fix
Mini bar / Now Listening:
- mini bar respects --app-sidebar-offset so it stops getting covered by
the desktop sidebar
- Now Listening adds overflow scroll + a labeled "Back to player" button
so controls are never inaccessible on short viewports
Library page:
- infinite scroll (replaces Previous/Next pagination)
- genre filter chip with X-to-clear
Scanner:
- 8-worker parallel reconcile (env SILO_AUDIOBOOK_SCAN_WORKERS to override)
- file-path-first dedup so cleaned titles don't collapse separate
narrations
- title cleanup at write time (strips "Read by X" / "(unabridged)"
suffixes; original tag preserved in original_title)
- audiobook_series upsert from tag-derived series_name/series_position
- secondary dedup check (author + narrator + year + duration ±0.5% +
title-prefix) so two folders of the same book attach to one row
Backend detail handler:
- new fetchAlsoByAuthor, fetchInSeries (with series row > 1 entry guard),
fetchSimilar (embedding-first with shared-genre fallback),
fetchOtherNarrations (regex-strips narrator suffix to group siblings)
- audiobookDetailResponse gained also_by_author, in_series,
similar_audiobooks, other_narrations fields
- list endpoint accepts a genre query param
Embeddings:
- BuildEmbeddingText branches on item.Type == "audiobook" to use
author/narrator credits instead of cast/director/writer
- mediaTypeLabel helper centralizes movie / "TV series" / audiobook
- ListEmbeddingTextCandidates SQL mirrors the Go branching exactly
- ItemsNeedingEmbedding and TotalMediaItemCount loosen status='matched'
gate to also include audiobooks (which don't go through TMDB match)
- FindSimilar gains a mediaType filter so cross-type results never appear
- callers in similar.go and personal.go pass the source item's type
Collections:
- MediaAudiobook MediaKind + audiobook(s) case in templateEligibleForLibrary
(stops offering broken movie/TV templates to audiobook libraries)
- useAddItemToCollection hook (user + admin/library endpoints)
- AddToCollectionDialog wired into audiobook detail and movie/series
ActionBar overflow menu
- ManualCollectionItemsEditor gained a search-and-add panel with
debounced live results
- QueryDefinition.media_scope, QuerySortRelevanceScope, ALL_MEDIA_SCOPES
extended to include "audiobook"
- CatalogFilterBar gained an Audiobooks media scope option
- parseCatalogMediaScope (backend) accepts "audiobook"
Migrations:
- 145_audiobook_series: per-book series_name/series_index with a
best-effort title-pattern backfill for the existing corpus
- 146_audiobook_title_cleanup: strips narrator suffix / (unabridged)
noise from existing titles, preserving raw in original_title
Scripts:
- scripts/dedup_audiobooks.py: one-shot merge for "Title" vs
"Title: Subtitle" duplicates, file-path-stable, dry-run by default
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
window.prompt for the admin decline reason was inaccessible (screen
reader announcements are inconsistent), unstylable, and blocked in
hardened environments. Replace with a controlled Dialog and a small
textarea so the decline UX matches the rest of the admin pages.
buildListQuery used a truthiness check on params.offset/params.limit
that silently dropped explicit zero values, leaving the URL out of
sync with the query key. Compare against null and positive numbers
instead.
Poster <img> elements had alt="" with no surrounding aria-label, so
screen readers reaching the cards via image-mode browsing got no
title. Use the poster's own title as the alt text.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Batch integration upserts in a single transaction
- Treat radarr/sonarr lookup results as arrays and require exact matches
- Prefer queue failures over downloading state when evaluating arr queues
- Allow retrying queued/downloading requests and block declines once fulfillment started
- Fall back to pending when auto-approval integration check fails
- Rename requests query hooks file and fix discover card request affordance
- Add `media_type=all` to request search, backed by TMDB `/search/multi` filtered to movies and series
- Default the Requests page filter to All and refresh search results grid styling
- Refine RequestPosterCard with status accent bar, richer fallback poster, and fluid grid layout
- Add GetMediaDetail TMDB client returning normalized detail with cast, crew, recommendations, and certifications
- Add /api/requests/detail/{media_type}/{tmdb_id} endpoint overlaying availability and request state
- Add RequestDetail page and link poster cards to it
- Treat empty/truncated Radarr/Sonarr POST responses as accepted; drop pre-submit existence lookups
- Add request domain, repository, service, and reconcile task
- Add Radarr/Sonarr fulfillment adapters and TMDB discovery
- Expose user and admin request APIs with quota and approval rules
- Add web UI for browsing, requesting, and admin queue management
- Migration 139 introduces media_requests and related tables
TypeScript types match the JSON shapes from the new
/api/v1/audiobooks endpoints (list, detail, progress). Three hooks:
useAudiobookLibrary (list), useAudiobook (detail), and
useReportAudiobookProgress (mutation that invalidates the detail
query on success so progress updates reflect immediately).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rename actor-oriented API, storage, and webhook fields to user/profile terminology
- Switch dev compose helpers to use docker-compose.yml
- Update frontend types and webhook sync settings for the new endpoints
- Rename actor-oriented API, storage, and webhook fields to user/profile terminology
- Switch dev compose helpers to use docker-compose.yml
- Update frontend types and webhook sync settings for the new endpoints
- Add admin job support for collection defaults applies
- Keep preview synchronous while real applies run via the job queue
- Show collection apply job status in the admin UI
- Add admin job support for collection defaults applies
- Keep preview synchronous while real applies run via the job queue
- Show collection apply job status in the admin UI