Repository navigation
fix(progress): keep played progress apart for same-titled episodes of one podcast - #504
Conversation
Reproduces #448 in real Obsidian with a feed that has two "Repeat Episode" items (40 s and 60 s media, distinct guids). It fails on this commit: the 40 s episode resumes at the 60 s one's position, one record holds both, and finishing one marks both played.
…es stay apart Two items of one feed with one title shared one played record, keyed by podcast name and title, so a rerun resumed where the original stopped and finishing one marked both played. The player also treated them as one episode, so playing the second while the first was paused resumed the first's live position. Progress is now written under a key made from the podcast name and the item's guid. Feed projection flags items whose title another item has (sharedTitle). A record saved under the title alone, as 2.21.1 saved all progress, still serves an episode whose title is unshared until its first write, and serves neither of two twins. A copy saved before guids, such as a 2.21.1 favorite, follows the only guid record of its podcast and title. Unplaying one twin leaves the other alone, and the Played list keeps twins apart and drops a title record that a guid record replaced. The player compares guids when both copies carry one. No load-time migration, no schema change, and no record is deleted. Related: #448
…n position Playing the second of two same-titled items while the first was paused took the same-episode path and resumed the first's live position. This guards the player's guid comparison in the unit suite, which the e2e test alone did not. Related: #448
…rogress A guid is unique only within its feed, so the progress key must stay scoped by podcast. Related: #448
…copies A copy saved before guids now matches a same-titled item only by its media, so it no longer hands its position to the other item. A row compares guids before media, so a rerun that reuses the original's audio can play. Played rows show their own record. The guid-less copy no longer looks up guid records by title: that lookup changed with every play and scanned the whole history on each row render. A shared-title mark that is not true is dropped when decoded. Related: #448
Deploying podnotes with
|
| Latest commit: |
8174b32
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://780abb40.podnotes.pages.dev |
| Branch Preview URL: | https://fix-episode-progress-key.podnotes.pages.dev |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 853c0f3. Configure here.
|
|
||
| for (const episode of episodes) { | ||
| const keys = [ | ||
| getProgressKey(episode), |
There was a problem hiding this comment.
Played list claims a twin finished
Medium Severity
A 2.21.1 title-keyed finished record is supposed to belong to neither same-titled episode, and feed rows honor that via sharedTitle. The Played list still includes that record until a guid key exists, and resolvePlayedEpisode maps it onto the first twin through the shared getEpisodeKey / title lookup. That row shows as played even though getPlayedEpisode will not use the record, so playback starts at zero.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 853c0f3. Configure here.
The cache keeps a feed's 75 newest items. Mark repeated titles on the full feed before the cut, as repeated guids already are, so the surviving twin still never borrows an old title record after a reload. Related: #448


Summary
Fixes #448
Two episodes of one podcast with the same title (two "Trailer" items, a rerun) shared one played record, so one resumed at the other's position and finishing one marked both played. There were two causes. Progress was keyed by podcast name plus title. The player also decided "same episode" by that key, so clicking the second episode kept the first one's live position.
sharedTitle, set when the feed is parsed) never borrows a title record, so one old record never goes to two episodes.There is no schema change, no migration step at load, and no write at load.
Left out, as follow-ups:
{{podcast}}/{{title}}. It still fails safely. This needs its own naming design.Verification
Obsidian 1.14.4 on Linux (Xvfb), isolated worktree vault, Node 22.23.2. Fixture feed "Journey Repeats" with two "Repeat Episode" items (40 s and 60 s WAV, guids
repeat-rerunandrepeat-original).npm run test:e2e -- -t "#448": red on master 791912f. The 40 s episode started at 30.07 s (the 60 s one's position), data.json held one record, and both rows were played. Green on this branch, including after a reload: the 60 s episode resumes at its own 30 s.npm run test:e2eon a fresh vault, run by an independent verifier at dcb2e1b: 32 files, 241 tests passed. After merging master (build: fail the build on legacy Svelte components and clsx #503, build tooling only; the bundle size is unchanged),-t "#448"passes again.npm run lint,format:check,typecheck,build,test(2788 passed),test:tooling(35 passed),docs:build: all pass.npm run size: 101,597 gzip bytes (master 101,332, +265; budget 103,000).released-2.21.1.jsonloads throughloadSettingswith an added same-titled pair that has one legacy record. The unshared episode resumes the legacy record and neither twin borrows it. The first save keeps every legacy key and adds one guid key. A raw 2.21.1 document adopted at runtime throughonExternalSettingsChangeis not rewritten, and its progress resolves for the guid episode.Mutations. Each one is a plausible wrong fix, and each fails the named tests:
sharedTitlenever setsharedTitlenot validated when decodedRelease impact
Storage: new guid-keyed entries in
playedEpisodesand asharedTitle: truemark on saved episodes. Schema version 2 is unchanged and old title keys stay as they are.With a device still on 2.21.1, both versions read each other's
data.json, and nothing is deleted or rewritten back and forth. Progress no longer passes between them for an episode that this version has saved. The older device sees that episode as it was before, and its later progress for it does not reach this version. Its "Mark as unplayed" still resets every record with the title, which includes both twins. Updating every device ends this. A feed that changes an episode's guid starts that episode's progress over, where the title key used to survive.Docs at release
docs/src/content/docs/sync.md, "Use PodNotes on two devices": add "Update PodNotes on every device. A device on 2.21.1 or earlier does not see the progress of an episode you played on 2.22.0 or later, and the newer device does not see that episode's progress from the older one."Note
Medium Risk
Changes how played progress is keyed, resolved, and synced; cross-device behavior can diverge between 2.21.1 and newer builds until all devices update, but schema version is unchanged and legacy keys are preserved.
Overview
Fixes #448: same-titled episodes in one podcast no longer share playback progress or played state.
Played progress is stored under
getProgressKey(podcast-scoped guid when present, otherwise the existing title key). Feeds mark duplicate titles withsharedTitle, and episodes with that flag do not inherit legacy title-key records—so one old row cannot apply to two feed items. Reads still resolve 2.21.1 title-key progress for a unique episode until that episode saves its own guid key; first save adds guid keys without removing legacy keys.Player and UI treat “the same item” via
isSameFeedItem(guid when both have one; otherwise media for shared titles), so switching between twins resumes the correct position. The Played list passesplayedKeyinto rows so guid-keyed records display even when lookup by episode alone would fail.Persistence validates
sharedTitleas strictlytrue. Queue/playlist identity is unchanged (still title-based)—called out as follow-up in the PR description.Reviewed by Cursor Bugbot for commit 853c0f3. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Keep separate playback progress for same-titled episodes in one podcast
getProgressKeyin episodeKey.ts, while guid-less episodes keep the old title-based key.sharedTitleflag, set in FeedRepository.ts and preserved through cache cuts inkeepNewestItems.playedKey) so rows render the exact guid-keyed record instead of falling back to title lookup.episodeStatus.getPlayedEpisodeAliasKeysandgetFinishedPlayedEpisodeRecordsif consumed out of tree, since legacy title-record aliasing rules changed for guid-bearing episodes.Macroscope summarized 8174b32.