Repository navigation
feat(search): list 20 results and add podcasts from Apple Podcasts links - #373
Merged
Merged
Conversation
Search asked iTunes for 3 shows, so a common keyword hid the wanted one. Ask for 20, drop shows without a public RSS feed since they cannot be added, and store collectionId as the string PodcastFeed declares. Refs #344
Pasting a podcasts.apple.com link was parsed as an RSS feed and failed with "Could not load feed". Recognize Apple Podcasts show links, resolve the show id through the iTunes lookup API, and offer the show's RSS feed as a result card. Shows without a public feed get a clear message. Fixes #344
iTunes search lists some feeds under several shows: "planet money" returns the NPR Planet Money feed three times. The result grid keys cards by feed URL, so Svelte threw each_key_duplicate and rendered nothing new. Keep the best-ranked show for each feed. Refs #344
Each keystroke pause starts a search, feed load, or Apple Podcasts lookup, and they can answer out of order. An older response replaced newer results, cleared "Searching..." while the newest request was still pending, and refilled the grid after the field was cleared. Only the latest input may now change the results, error, notice, or progress. Refs #344
The parser took the first `id<digits>` path segment, so podcasts.apple.com/us/podcast/id10/id1545953110 looked up show 10. The show id is the final path segment in Apple Podcasts links. Refs #344
The iTunes lookup with entity=podcast on an artist id returns the artist and that artist's shows, so an artist id resolved to one of its shows (706424103 gave "Apple Events"). Return only the show whose id was looked up. Refs #344
Search is the only caller that can get one feed under several shows. The shared iTunes fetch now returns every mapped feed, and the show lookup keeps picking the show whose id it looked up. Refs #344
The newer search answered with Planet Money, whose feed iTunes lists three times, so a dedupe regression also failed this race test. Answer with a single-show response instead. Refs #344
No test failed when the Apple Podcasts lookup branch lost its latest-request guards. A lookup that resolves after a newer search now must leave that search's results, with no alert and no notice. Refs #344
…unce The latest-request counter only moves when the debounced handler runs, so for 300 ms after an input change the previous search still counts as latest. Its answer fills the grid with results for the old input. This test fails until the counter moves on every input change. Refs #344
The latest-request counter moved only when the debounced handler ran, so for 300 ms after an input change the previous request still counted as latest. Its response could fill the grid, end "Searching...", or show a notice for the old input. Count each input change at once and let the debounced handler use the count it finds. Refs #344
The debounced handler read the latest-request counter itself, so it was correct only if every caller bumped the counter first. Count the input change at the call site and pass it with the value, so the handler compares the count it was called with. Refs #344
The newer search in the race test answered with the Huberman Lab lookup fixture. Record the real iTunes search for "huberman lab", which returns that one show, so the search is answered by a search response. Refs #344
…is pending The lookup used to answer after the newer search had finished, so the search had already cleared "Searching..." and the lookup's finally guard went untested. Resolve the lookup first: the grid keeps searching and shows no notice, then the search's results arrive with no alert. Refs #344
Deploying podnotes with
|
| Latest commit: |
38bcfc1
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://f03a1d7a.podnotes.pages.dev |
| Branch Preview URL: | https://feat-search-apple-links.podnotes.pages.dev |
Saved feeds are keyed by title, so adding a search result that shares a saved podcast's title silently replaced that subscription. Searching "planet money" lists NPR's Planet Money and a namesake by another author; adding the second overwrote the NPR feed. Adding a podcast whose title is already saved now leaves the saved feed untouched. Re-adding the same feed is a no-op, compared by the saved feed's resolved URL so a private feed's placeholder still matches its pasted URL. A different feed is refused with a Notice before its URL is interned into SecretStorage.
Search can list several shows with the same title, such as NPR's Planet Money and a namesake by another author. Each result card now names the show's author in one muted line under the title, taken from the iTunes artistName or the feed's own author.
A search term with "&", "+" and non-ASCII characters must reach iTunes as one term next to the podcast filters. A failed Apple Podcasts lookup must show and announce the retry message.
A search result titled like an Object member, such as "toString", read as already saved, and adding it threw. Saved feeds are now looked up by own key only, both for the card's Add/Remove state and when adding. The card and the add guard now share one predicate, isSavedFeed. A followed private feed matches both its placeholder and the URL it stands for, so pasting its private URL shows Remove instead of a dead Add button, and the saved list still shows Remove. When a saved private feed's URL is missing on this device, adding a feed under its title now shows the existing re-add message instead of claiming a different podcast, and stores nothing.
…cretStorage A refused private namesake must store none of its URL, so the test fails if the URL is interned before the guard runs. The namesake test now clears the Notice spy before the namesake click. The test Button mock dispatches one click more than once, so without the clear a notice from the first add could satisfy the assertion.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #344.
Podcast search in Settings > PodNotes now lists up to 20 shows instead of 3. A common keyword no longer hides the show the user wants.
The search box also accepts Apple Podcasts show links. PodNotes reads the show id from the link and asks
https://itunes.apple.com/lookup?id=<id>&entity=podcastfor the RSS feed. The show then appears as one card, and the user adds it as usual. Before this change, a link likehttps://podcasts.apple.com/us/podcast/huberman-lab/id1545953110failed with "Could not load feed: Invalid RSS feed".Supported link shapes:
podcasts.apple.comand the legacyitunes.apple.comhost?uo=4or?i=<episode>(an episode link adds its show)id=form (.../viewPodcast?id=<id>,.../podcast?id=<id>)If Apple does not know the id, or the show has no public RSS feed, the user sees "No public RSS feed found for this Apple Podcasts link." If the lookup request fails, they see "Could not look up this Apple Podcasts link. Please try again."
Raising the limit to 20 exposed three problems, and this PR fixes all of them:
each_key_duplicateand the grid did not update. Search now keeps each feed once, under its best-ranked show.The URL parsing and the lookup are plain TypeScript in
src/iTunesAPIConsumer.ts, so the planned view redesign can reuse them unchanged. Both requests go through the existing boundednetworkRequestboundary. Search and lookup results are now saved with a stringcollectionId, which matchesPodcastFeed. The API returns a number.Verification
Tested in Obsidian 1.13.7 on Linux, headless, in this worktree's isolated vault (
npm run start:e2e-obsidian, project verify skill), driven through Settings > PodNotes > Search Podcasts.Before the change (master
1080891):After the change, starting from a fresh vault:
"huberman" lists all 13 shows iTunes returns. "history" lists 18: iTunes returned 20, and 2 of them have no

feedUrl."planet money" lists 10 shows, each feed once. An intermediate build without dedupe threw

each_key_duplicatehere.Pasting
https://podcasts.apple.com/us/podcast/huberman-lab/id1545953110?i=1000650000000shows the Huberman Lab card. Clicking Add saves this entry indata.json:"Huberman Lab": {"url": "https://feeds.megaphone.fm/hubermanlab", "collectionId": "1545953110", ...}The added podcast opens in the PodNotes view with 448 episodes, which matches Apple's

trackCount.Pasting

https://podcasts.apple.com/us/podcast/apple-news-today/id1473872585shows "No public RSS feed found for this Apple Podcasts link." That show is Apple-hosted and has nofeedUrl.obsidian dev:errorsreports "No errors captured."Namesake refusal, through trusted mouse clicks:
data.jsonholds{"url":"https://feeds.npr.org/510289/podcast.xml","collectionId":"290783428","author":"NPR"}.data.jsonis unchanged.obsidian:e2e -- --reload. Both the in-memorysavedFeedsanddata.jsonstill hold the NPR entry.Before this fix (head

6189d80), the same clicks replaced the entry withhttps://www.spreaker.com/show/7445480/episodes/feedandcollectionId6820776004, with no notice:After the fix:

screen recording (contact sheet)
The Apple link flow still adds Huberman Lab on the final head, now with
authorScicomm Media.After merging master, a smoke run on the merged build (plugin 2.20.3) still lists 10 cards for "planet money". Pasting
https://podcasts.apple.com/us/podcast/the-daily/id1200361736shows The Daily card. No errors were captured.The link-to-add flow is recorded here: screen recording (contact sheet).
Commands, all passing at head
f2a760c(master6a6ae2dmerged):The tests use iTunes JSON recorded from the live API on 2026-10-11, stored in
tests/fixtures/itunes/:The URL parser is tested against real link samples, both accepted and rejected. Search-term encoding is tested with "Ça & va + bien". The Apple lookup-failure notice has its own test. The namesake guard is tested for these cases:
toString)Every new test was confirmed to fail when the defect it names is put back.
The changes went through multi-model review, with Opus 5.5 and GPT 6 Astra as reviewers. The original change had four review rounds. The namesake fix, from an independent verifier's finding, had two more. Fable 5.1 was rate-limited, so Opus 5.5 took the Claude seat. All blocking findings are fixed. One commit,
c1d085a, is red on purpose: it adds a test before the fix that makes it pass.Release impact
There are no settings migrations, API changes or URI changes.
Feeds added from search or an Apple link now store
author(the iTunes artist name) and storecollectionIdas a string. Before, it was a number at runtime even though the type says string. The persistence codec already converts a stored number to a string on load and logs a warning (src/persistence/collectionCodecs.ts). New feeds no longer hit that conversion, and existing saved feeds load as before.Note
Add Apple Podcasts link lookup and raise iTunes search limit to 20
queryiTunesPodcastsin iTunesAPIConsumer.ts now requests 20 results instead of 3, omits results without a feed URL, de-duplicates by exact feed URL, and includes the artist name as author on result cards in PodcastResultCard.svelte.toString) as ordinary keys.queryiTunesPodcastsconsumers if the 3-result cap mattered.Macroscope summarized 38bcfc1.