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

+11 -4
View File
@@ -117,10 +117,10 @@ func MediaBadgeIcon(badge string) string {
func TotalMediaCount(libs []LibraryData) int64 {
var total int64
for _, l := range libs {
// Unmounted storage contributes nothing visible: an offline
// library's holdings stay in the database but out of the UI
// until its folders return.
if l.Offline {
// Podcasts libraries are feed-managed (plan D13): not book-
// library inventory — excluded here so switcher totals stay
// consistent with the switcher's podcast-filtered option list.
if l.Offline || l.TypeName == "podcasts" {
continue
}
total += l.MediaCount
@@ -128,6 +128,13 @@ func TotalMediaCount(libs []LibraryData) int64 {
return total
}
// IsBookLibrary reports whether a library type participates in the
// generic book surfaces (switcher, All Books, series). Podcasts are
// feed-managed and surface via /podcasts only (plan §9/D13).
func IsBookLibrary(typeName string) bool {
return typeName != "podcasts"
}
func uuidToString(id pgtype.UUID) string {
if !id.Valid {
return ""