Skip to content

feat(info): Load missing episodes via RSS supplement (keep PI ownership) #969

Description

@ashwkun

Summary

Podcast Info for Podcast Index (PI) shows only lists episodes from the proxy → PI API. When PI’s crawl lags the publisher feed, episodes look “missing.”

Goal: Let users Load missing episodes from the show’s public RSS feedUrl while keeping the PI podcast id as the library/automation identity (notifications, autodownload, sync, recs stay intact).

This is an app-only bug-fix / completeness path, not “subscribe by RSS.”

Problem

  • PI Podcast Info loads episodes via GET /episodes (uncached PI passthrough). Freshness ≈ PI crawl lag (+ app’s ~5‑minute in-memory page cache).
  • GET /podcast already returns feed.url (mapped to Podcast.feedUrl), but the app does not fetch/parse that feed for PI shows.
  • Existing Settings Add RSS feed creates an rss: subscription and can migrate/retire the PI sub — which turns off FCM notifications and per-show autodownload. Wrong tool for “catalog list is incomplete.”

Product contract (hard rules)

Supplement (this issue) Subscribe (Settings — unchanged)
Intent Fix incomplete PI episode lists User opts into feed-owned library entry
Podcast id Stay on PI id New rss: id
Library / FCM / autodownload / sync / recs Unchanged Weaker / disabled by design
Implementation RssFeedClient.fetch + parse only RssPodcastRepository.addSubscription (+ migrate)
Copy “Load missing episodes” “Add RSS feed” / “Use RSS source”

Must never from this flow:

  • Call addSubscription / confirmPodcastIndexLink / migrateLinkedState / retireLinkedPodcastIndexSubscription
  • Create a second Library tile
  • Disable PI notifications or autodownload

Proposed UX

  • Where: Podcast Info top overflow (⋮), alongside Mark all / Hide completed
  • When: !podcast.isRss and HTTPS feedUrl present (after details load). Hidden for rss: shows (they already own the feed + pull-to-refresh). Linked PI→RSS redirect already loads the RSS catalog — button stays hidden there.
  • Confirm copy: Fetches this show’s public feed and adds episodes that are not in the catalog list. Subscription and new-episode notifications stay the same.
  • Result toast: Added N / Already up to date / Couldn’t load feed

Technical approach (app-only; no proxy changes)

Why no proxy work

Verified on live api.aswin.cx and in proxy/:

  • /podcast returns PI feed.url (CF Cache API ~1h for metadata only)
  • /episodes is uncached PI episodes/byfeedid passthrough
  • Proxy RSS hydrate is only on /podcast/meta (channel tags), not episode lists
  • Turso is a small curated slice — not a place for per-PI override flags for the long tail

Storage

Do not reuse rss_episodes (subscription catalog, FK to podcasts).

New Room tables (migration 30 → 31):

  1. episode_supplements — PK = PI podcast id; feedUrl, rssNamespaceId (rss:<sha256> for id generation), optional validators, fetchedAt
  2. episode_supplement_items — PK = negative episode id; podcastId = PI owner; episode payload fields

Identity

  • Generate episode ids via RssIdGenerator.episodeIdForPodcast(rssNamespaceId, …) (requires rss: namespace; negative Long strings — no collision with positive PI ids)
  • Map to Episode with podcastId = PI id (history/queue/automation stay on the show)
  • Extend getEpisodeImpl: negative id → RSS catalog → supplement store fallback (so play / Episode Info / downloads resolve)

Algorithm

  1. Validate HTTPS feed URL
  2. RssFeedClient.fetch + parse under rssNamespaceId
  3. Load PI baseline (~up to 1000 via existing episodes API) for dedupe
  4. Drop matches using RssSourceMatcher-style heuristics (exact audioUrl → unique title → title + pubDate ±1 day)
  5. Replace prior supplement rows for that PI id; merge into Podcast Info list on load / load-more / sort
  6. Search: include supplement titles when supplements exist

Module ownership

Layer Responsibility
:core:database Tables + migration
:core:rss EpisodeSupplementRepository (never subscription)
:core:catalog Episode lookup fallthrough
:feature:info Overflow + confirm + VM merge
:app Composition-root wiring
proxy/ Untouched

Explicit non-goals (v1)

  • Proxy / Turso / server “stale podcast” lists
  • Auto-run on every Podcast Info open
  • Changing Settings Add RSS / link-migrate
  • Re-enabling FCM for RSS-subscribed shows
  • Writing feed episodes under PI id into rss_episodes

Docs / tests when implemented

  • core/rss/README.md + feature/info/README.md: subscription vs supplement
  • ARCHITECTURE.md: supplement identity note
  • Unit tests: dedupe/merge, no subscription side effects, lookup fallthrough, overflow gating
  • installDebug for manual check

Acceptance criteria

  • On a PI show with HTTPS feedUrl, overflow offers Load missing episodes
  • Confirm explains features stay the same (not a subscribe/switch)
  • Missing feed-only episodes appear in the list and are playable / queueable / downloadable
  • PI subscription, FCM topic, and autodownload flags are unchanged
  • No new Library row / no rss: subscription created
  • Settings Add RSS behavior unchanged
  • Action hidden for rss: shows and when feedUrl missing / non-HTTPS

Notes

Related discussion: keep “fix incomplete PI list” separate from “user commits to RSS ownership for stability / offline catalog.” Longer-term proxy freshness is optional and out of scope here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Major SeverityenhancementNew feature or request

    Type

    No type

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions