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>
Phase-3 of the collection-templates expansion. Adds ten curated movie
franchises plus a generic placeholder template, all backed by Agent B's
tmdb_collection source. Templates land in the 7000s sort-order band.
Verified TMDB collection IDs (via api.themoviedb.org/3/collection/{id}):
- 10 Star Wars Collection (9 films)
- 645 James Bond Collection (25 films)
- 1241 Harry Potter Collection (8 films) — used for "Wizarding World"
- 9485 The Fast and the Furious Collection (12 films)
- 119 The Lord of the Rings Collection (3 films)
- 121938 The Hobbit Collection (3 films)
- 328 Jurassic Park Collection (7 films)
- 295 Pirates of the Caribbean Collection (5 films)
- 87359 Mission: Impossible Collection (8 films)
- 535313 Godzilla Collection (3 films) — used for "MonsterVerse"
Plan corrections / skips:
- MCU (plan ID 86311) resolves to "The Avengers Collection" (6 films),
not the full Marvel Cinematic Universe. TMDB has no umbrella MCU
collection. Skipped per the no-invent-IDs rule. Sort slot 7001 left
open.
- DC Extended Universe (plan ID 573436) resolves to "Spider-Man:
Spider-Verse Collection" — unrelated. No DCEU umbrella exists on
TMDB. Skipped. Sort slot 7005 left open.
- Wizarding World (plan ID 1241) is TMDB's Harry Potter octalogy;
Fantastic Beasts lives in a separate collection (435259). Used as-is
with description noting the scope.
- MonsterVerse (plan ID 535313) is labelled "Godzilla Collection" but
contains the three Legendary MonsterVerse Godzilla films, which is
the closest TMDB representation. Used as-is.
The placeholder template ships with CollectionID=0 and an empty sync
schedule so it can be applied without a valid franchise ID; admins edit
the resulting collection's source_config before the first sync.
Append the Phase-2 catalog from the collection-templates expansion plan:
- 18 Popular by Genre (sort_order 5001..5018, daily 0 4 * * *,
popularity.desc with vote_count_gte=300)
- 18 Top Rated by Genre (sort_order 6001..6018, weekly 0 5 * * 0,
vote_average.desc with vote_count_gte=1000)
- 1 Kids Movies (sort_order 9050, weekly, Family genre with
certification_lte=PG)
Adds TestPhase2DiscoverTemplatesUseExpectedBands to pin the band ranges
and the TMDB query parameters that define each section.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>