feat(podcasts): generic-surface exclusions — podcasts never render as books (task 1.3)

§9 / D3 / D13 / D15 / D16: podcasts-type libraries are excluded from
every generic book surface. Under RLS these are pure UX/type filters
(engine already hides other users' episodes); their job is preventing
episode grids in surfaces designed for books. The discriminator is the
denormalized media_items.library_type_name (IS DISTINCT FROM 'podcasts'
— NULL rows predate the column and stay visible).

SQL (queries.sql, marked '-- podcasts exclusion (plan §9)'):
ListMediaItems/ListMediaItemsSorted (All Books) · SearchMediaItems +
SearchMediaItemsUnified (generic search) · FacetCountsUnderFilters (all
14 arms) + the six Search*Values facet-suggestion queries ·
GetDistinctSeries + Count (series pages) · GetLibraryItems (collections
picker) · dashboard system sections (Continue Reading / Recently Added
/ Recently Read / Not Started) · GetPopularBooks + GetTopBooksByMinutes
(book rankings must not rank episodes).

Go: statistics summary counts episodes' sessions into time-spent (D15
keeps that) but skips them in BooksActive/BooksFinished — an episode is
not a book. OPDS filters podcasts libraries via one helper at all 7
library-walk sites (D16).

Templ: LibrarySwitcher skips podcasts entries and TotalMediaCount
excludes them — presentation-layer filter so every switcher consumer
(dashboard, collections, custom-section builder) is correct at once
(D13); podcasts surface via /podcasts only.
This commit is contained in:
John O'Keefe committed 2026-10-29 15:44:48 -04:00
1 parent c0e6993273
commit 9a148c2344
8 files changed
+174 -47

No files matched your search

+7
View File
@@ -26,6 +26,13 @@ templ LibrarySwitcher(libData []LibraryData, currentLibraryID string, title stri
>
<option value="">All Libraries ({ TotalMediaCount(libData) })</option>
for _, lib := range libData {
// Podcasts libraries are feed-managed (plan D13): never
// in the book-library switcher — feeds surface via
// /podcasts only. Presentation-layer filter so every
// switcher consumer is correct at once.
if !IsBookLibrary(lib.TypeName) {
continue
}
if lib.Offline {
if lib.ID == currentLibraryID {
<option value={ lib.ID } selected>{ lib.Name } ({ lib.MediaCount }) — storage offline</option>