* feat(sections): fix broken home section templates and add six new ones
Fixes templates that silently produced nothing:
- award_winners: hide from gallery (resolver is a stub until award data
exists); saved sections keep resolving
- seasonal_themed: christmas/st_patricks/thanksgiving get an interim
title-keyword resolver, and multi-theme selection skips themes without
an executable query so a data-less theme can no longer black out the
section during its own window (previously killed the section all of
December)
- taste_match: empty genre now auto-picks the profile's strongest taste
cluster (fallback: server top genre); the default preset was permanently
empty
- because_you_watched: honor the recipe's anchor_item_id key (fetcher only
read legacy source_item_id, so pinning an anchor did nothing)
- editorial_spotlight: reject subject_type=franchise (validated but could
never resolve); fix drawer misrepresenting pinned presets as auto-rotate
- admin_curated_list: add a catalog-search item picker so Editor's Picks
is actually addable; block saving an empty list; hide admin_only recipes
from profile-facing galleries
- discovery fetchers (hidden_gems, forgotten_favorites,
critically_acclaimed): honor single/multi library scope, intersected
with viewer access; implement hidden_gems max_play_count
New templates: returning_shows (new season of shows you've watched),
genre_roulette (rotating top-genre spotlight with title override),
anniversaries (milestone release anniversaries this month), short_watches
(well-rated movies under a runtime cap), family_movie_night seasonal
theme (Fri/Sat evenings), and a "New in 4K" format_showcase preset via a
new sort=recent param.
Adds a blanket test asserting every visible gallery preset's defaults
pass its own recipe validation — the gap that let taste_match and
Editor's Picks ship broken. New SQL shapes validated with EXPLAIN against
the dev database.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(sections): address PR #332 review findings
Codex review:
- returning_shows: the new-season file check now applies the effective
library scope (section scope ∩ viewer-allowed, minus disabled) to
media_files.media_folder_id, so an episode file that only exists in an
out-of-scope folder can no longer surface the series
- buildLibraryScope: replaced the media_item_libraries row join with
EXISTS / NOT EXISTS semi-joins. An item in several in-scope libraries
now yields exactly one row in the non-GROUP BY rails (short_watches,
anniversaries, seasonal keyword, format_showcase, new_to_library, ...),
and the disabled-library check is item-level, closing the join-row leak
where membership in an allowed library masked membership in a disabled
one. Deny-only mode keeps the positive-membership guard, mirroring
catalog's appendDiscoveryLibraryScope.
CodeRabbit review:
- recommendations reader: a taste cluster whose cached items are entirely
filtered out now falls through to the next cluster / global fallback
instead of returning an empty row
- genre_roulette: multi-library scopes get distinct rotation seeds
- returning_shows: reject negative lookback_days at validation
- shared oneOf() enum validator replaces per-recipe switch duplication
- SeasonalTitleOverride usable-filter contract covered by a direct test
- web NumberParamField: integer-only guard + step=1 (backend fields are
Go ints; fractional values failed unmarshalling at save)
- curated list picker: search failures show an error instead of a
misleading "No matches."; pre-existing item_ids hydrate display titles
via the watch-detail endpoint instead of rendering raw ids
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* perf(jellycompat): fast-paths for slow Jellyfin-compat queries
Rebuilds the jellycompat slow-query fast-paths cleanly on main, without
the hub-sections virtual-library feature (which is intentionally absent
on this branch). Each path is bounded so behavior stays predictable
under load.
Fast-paths:
- Episode catalog: add the load-bearing `si.type = 'series'` hydration
guard plus a matching entry-scan series-parent guard, so the page and
count scans stay aligned with hydration (no under-filled pages, no
inflated TotalRecordCount from podcast/audiobook episode rows).
- /Shows/{id}/Episodes AdjacentTo: serve a bounded prev/self/next
neighbor window with series-membership validation and a cross-series
id guard, falling back to the full listing.
- Resume: serve Continue Watching through the capped native
continue-watching fetcher (next-up suppressed, in-progress only)
instead of loadProgressPage's unbounded scan. Wires only the
sections read-time fetcher (StoreProvider) into ItemsHandler; no
hub-section/virtual-library exposure.
- Played-history: push the type + library filter down into SQL.
- recently_added: fan a no-parentId browse out across libraries with an
app-side dedup merge, clamped to BrowseFilters.MaxLimit, and exclude
compat-hidden (audiobook/podcast) libraries from the fan-out.
- Detail fields: batch per-item GetItemDetail in the Latest/browse
detail-field paths via GetItemDetailsByIDs, with per-item fallback.
- Search: term-length and result-count guards for aux search.
* perf(jellycompat): clamp Resume fast-path fetch to maxResumeItems
A large client-supplied StartIndex on /UserItems/Resume inflated
loadResumeViaSections' fetchLimit (pageSize + StartIndex) and fed it
straight into ResolvedSection.ItemLimit, letting the continue-watching
fetcher scan up to its continueProgressMaxScanned hard cap (1000 rows)
instead of stopping near maxResumeItems. Clamp fetchLimit to
maxResumeItems and short-circuit to an empty page when StartIndex is at
or beyond the cap (no visible row can land there), keeping the capped
fetcher's scan bounded and predictable under load.
Addresses CodeRabbit review finding on PR #247.
* feat(audiobooks): redesign audiobook library around resume and series progression
Audiobook libraries previously reused the video-shaped library page: a
backdrop carousel hero (audiobooks have square covers and no backdrops),
movie-style default sections, and a browse grid whose primary audiobook
axes (author, narrator, series) were buried as filters.
Backend:
- New next_in_series section type: surfaces the next unstarted book, by
series_index, in series the profile has finished a book of, ordered by
most recent finish. Registered as a library-staple recipe.
- New GET /api/v1/catalog/audiobook-groups endpoint: grouped browse by
author/narrator/series with book count, total duration, per-profile
progress counts, and poster URLs for cover stacks.
- Audiobook library defaults: continue-listening is featured (renders as
the Now Listening hero) with next-in-series directly after it. A data
migration upgrades existing audiobook libraries, skipping layouts where
an admin already featured a section.
Frontend:
- NowListeningHero replaces HeroBanner for audiobook libraries: resume
deck with chapter position, hours left, ambient color from the cover,
and one-click resume; remaining in-progress books render as the
Continue Listening row.
- Library tab gains Books/Series/Authors/Narrators browse axes persisted
via the type param; selecting a group drops into the Books grid with
the matching filter applied.
- "Recommended" tab is labeled "Home" for audiobook libraries; audiobook
continue cards use square covers and hr/min time-left formatting.
- Shared audiobook chapter/file/duration helpers extracted to
web/src/lib/audiobooks (deduplicated from AudiobookContent).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(audiobooks): address review feedback on library redesign
- Push library scoping into the next-in-series candidate SQL so finished
series whose next book lives in another library can't consume the
candidate limit and starve a library-scoped section (Codex P2).
- Paginate the audiobook groups fetch until the server-reported total is
reached (500/page, 20-page bound) so client-side filtering sees the
complete author/narrator/series list (Codex P2, CodeRabbit).
- Make the redesign migration rollback-safe: rows the Up touches carry
config markers (featured_by_migration / seeded_by_migration) and the
Down reverts only marked rows, leaving admin-set featured state and
hand-created next_in_series sections alone (Codex P2, CodeRabbit).
- Gate NowListeningHero's detail-derived files/credits on the detail
matching the deck item, so Resume can't start the new book with the
previous book's files while keepPreviousData shows stale detail
(Codex P2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
A library-agnostic home section that surfaces external global trending
(TMDB or Trakt, admin-selectable) mixing movies + series, matched to
titles in the viewer's enabled libraries. TMDB uses /trending/all/{window}
(natively mixed); Trakt merges trending movies + shows. Fetched live with
a 1h in-process cache, so no background job or stored collection — and no
per-library duplication.
Appears in the admin section gallery via its recipe presets (TMDB Trending
Today/This Week, Trakt Trending); featured -> hero via the existing flag.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>