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):
episode_supplements — PK = PI podcast id; feedUrl, rssNamespaceId (rss:<sha256> for id generation), optional validators, fetchedAt
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
- Validate HTTPS feed URL
RssFeedClient.fetch + parse under rssNamespaceId
- Load PI baseline (~up to 1000 via existing episodes API) for dedupe
- Drop matches using
RssSourceMatcher-style heuristics (exact audioUrl → unique title → title + pubDate ±1 day)
- Replace prior supplement rows for that PI id; merge into Podcast Info list on load / load-more / sort
- 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
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.
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
feedUrlwhile 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
GET /episodes(uncached PI passthrough). Freshness ≈ PI crawl lag (+ app’s ~5‑minute in-memory page cache).GET /podcastalready returnsfeed.url(mapped toPodcast.feedUrl), but the app does not fetch/parse that feed for PI shows.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)
rss:idRssFeedClient.fetch+parseonlyRssPodcastRepository.addSubscription(+ migrate)Must never from this flow:
addSubscription/confirmPodcastIndexLink/migrateLinkedState/retireLinkedPodcastIndexSubscriptionProposed UX
!podcast.isRssand HTTPSfeedUrlpresent (after details load). Hidden forrss:shows (they already own the feed + pull-to-refresh). Linked PI→RSS redirect already loads the RSS catalog — button stays hidden there.Technical approach (app-only; no proxy changes)
Why no proxy work
Verified on live
api.aswin.cxand inproxy/:/podcastreturns PIfeed.url(CF Cache API ~1h for metadata only)/episodesis uncached PIepisodes/byfeedidpassthrough/podcast/meta(channel tags), not episode listsStorage
Do not reuse
rss_episodes(subscription catalog, FK topodcasts).New Room tables (migration 30 → 31):
episode_supplements— PK = PI podcast id;feedUrl,rssNamespaceId(rss:<sha256>for id generation), optional validators,fetchedAtepisode_supplement_items— PK = negative episode id;podcastId= PI owner; episode payload fieldsIdentity
RssIdGenerator.episodeIdForPodcast(rssNamespaceId, …)(requiresrss:namespace; negative Long strings — no collision with positive PI ids)EpisodewithpodcastId= PI id (history/queue/automation stay on the show)getEpisodeImpl: negative id → RSS catalog → supplement store fallback (so play / Episode Info / downloads resolve)Algorithm
RssFeedClient.fetch+parseunderrssNamespaceIdRssSourceMatcher-style heuristics (exactaudioUrl→ unique title → title + pubDate ±1 day)Module ownership
:core:database:core:rssEpisodeSupplementRepository(never subscription):core:catalog:feature:info:appproxy/Explicit non-goals (v1)
rss_episodesDocs / tests when implemented
core/rss/README.md+feature/info/README.md: subscription vs supplementARCHITECTURE.md: supplement identity noteinstallDebugfor manual checkAcceptance criteria
feedUrl, overflow offers Load missing episodesrss:subscription createdrss:shows and whenfeedUrlmissing / non-HTTPSNotes
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.