Repository navigation
feat(notes): find notes and links by episode GUID, capture where you write, and create episode notes without playing - #442
Merged
Merged
Conversation
Capture Timestamp, Capture Last 10/20 Seconds and the headphones' previous-track button now share one CaptureService. A capture goes to the focused editor, else to the markdown editor focused last (clicking in the PodNotes view leaves no active editor), else to the episode note, which is created from the note template when missing and appended to atomically. The three commands become plain callbacks so they run with no editor focused. Saved mobile toolbar entries are looked up by command id, so they keep working.
…nd episode picker Create episode note is now available with nothing loaded once a podcast is saved. It asks for a podcast, loads its feed, asks for an episode in feed order and creates that episode's note, without touching the player. With an episode loaded it creates that episode's note as before. FeedSuggestModal becomes the generic PickerModal, shared by the feed note and episode note commands.
A guid can repeat within a feed, so the loaded-episode shortcut no longer accepts the loaded episode on a guid match alone. A guid that differs from the loaded episode's sends the link through the feed lookup, which refuses duplicate guids and falls back to the title.
A template that writes "feedUrl": or 'guid': already has the key, so adding it again would give the note a duplicate key that Obsidian rejects.
A MarkdownView in Reading view still returns its hidden editor, and text written there is lost. A capture now treats such a note as no editor, whether it is focused or was the last note focused, and goes to the episode note.
A subscription of another feed that shares the episode's podcast name no longer lends its transcript through a shared guid. Other feeds still match by enclosure URL as before.
createPodcastNoteFileIfNotExists now returns the note and whether it was created, and shows no notice. createPodcastNote says an existing note exists, as before, and a capture into an existing note stays quiet. A capture scans the vault once instead of three times.
The episode picker for Create episode note now loads a podcast by its savedFeeds key, so a podcast saved under another name than its title still lists its episodes. A private podcast whose address is not on this device gets the usual private feed notice.
Capture commands now run from anywhere, Create episode note works with nothing loaded once a podcast is saved, and fixtures.mjs has a retitle verb. The verify skill said otherwise or did not list it.
…d matches A loaded episode without a guid may be another feed episode that reused its title, so a link that carries a guid now goes through the feed lookup unless the loaded episode has the same guid.
Creating a note awaits the chapter fetch, during which sync or another capture can write the episode's note. Look the note up again after that wait and return it instead of writing a second note.
When vault.create fails because the note now exists at its path, the note is returned with created: false, so Create podcast note says it already exists and a capture does not report a new note.
… capture destinations J-NOTE-09 retitles an episode on the fixture server and proves its note, its guid= timestamp link and its Resume link still find it, while an old link without a GUID still plays its episode. J-NOTE-10 creates an episode note from the palette's podcast and episode pickers with nothing loaded. J-TS-08, J-TS-09 and J-TS-10 prove Capture Timestamp lands at the caret of the editor used last, in a popout too, and otherwise in the episode note. The fixture server can now retitle an episode, and the journey actions can run a command from the palette, open and click an editor in any window, and close a popout with its tab. Closing a popout can leave Obsidian looping a ResizeObserver in the main window, with PodNotes disabled too, so the popout journey reloads at the end and accepts only that error.
Deploying podnotes with
|
| Latest commit: |
62c16e5
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://997fc06d.podnotes.pages.dev |
| Branch Preview URL: | https://feat-note-identity.podnotes.pages.dev |
| suggestion: (text: string) => byCss(`suggestion ${text}`, ".prompt .suggestion-item", text), | ||
| paletteCommand: (name: string): UiTarget => ({ | ||
| name: `palette command PodNotes: ${name}`, | ||
| elements: `[...document.querySelectorAll(".prompt .suggestion-item")].filter((el) => el.querySelector(".suggestion-prefix")?.textContent === "PodNotes" && el.querySelector(".suggestion-title")?.textContent === ${q(`PodNotes${name}`)})`, |
Owner
Author
There was a problem hiding this comment.
Not a defect here. paletteCommand builds renderer code for the e2e harness, and its only input is a command name written as a literal in the test ("Create episode note", "Capture Timestamp"), quoted with JSON.stringify like every other ui target in this file. CodeQL classifies the location as test code, and master already has 12 open alerts of this rule for the same pattern in tests/e2e/journey/actions.ts, tests/e2e/harness.ts and tests/e2e/local-files-runtime.test.ts. Clearing the class means one escaping quote helper for the whole harness, which belongs in its own change rather than in one new target.
Two items of one feed can carry the same guid. Projected onto both episodes, it let a note of one episode open for the other, and become its capture destination. A repeated guid now identifies neither episode, so their notes and links fall back to the path and title rules.
…akes focus Every markdown tab that took focus became the remembered editor, a note in Reading view too, so opening one sent the next capture to the episode note while the editor stayed open. Obsidian fires no event when a note switches between Reading view and editing, so a note now counts by its mode when focus leaves it, and only an open note in Source or Live Preview mode receives a capture.
Obsidian's parseYaml is eemeli/yaml 2. The obsidian mock now parses with the same library and options, so unit tests see the YAML Obsidian sees.
… holds A line pattern decided which keys a template's frontmatter already had, so an indented key or an escaped one such as "gu\u0069d" got a second, unindented guid or feedUrl, and Obsidian showed Invalid properties. Existing keys now come from Obsidian's own YAML parser, new keys take the indentation of the mapping's first key, and frontmatter that is not a mapping, or would not read back as the same mapping plus the fields, is left as it is.
…view, and indented frontmatter
The mini player's Timestamp now goes through CaptureService: ShellServices.captureTimestamp returns the Capture result, and MiniPlayer names the note from its path. #434's captureTimestampToEditorOrNote and its capture helpers in main.ts are gone, IPodNotes exposes `capture` instead, and its two main.ts capture tests now drive capture.timestamp(). The NOTE1 journeys open the Library with showLibrary, as the shell replaced the grid tab.
This was referenced Oct 13, 2026
chhoumann
added a commit
that referenced
this pull request
Oct 13, 2026
…s pages (#447) * test(verify): capture the v3 pages at the design's widths and themes screens.mjs places the view at sidebar 320, pane 820 and phone 390, switches theme, seeds a scene and shoots the five UI1a states, with a contact sheet beside the prototype's screen. fixtures.mjs gains a library manifest of eight podcasts, one whose feed answers 404. * refactor(ui): add library actions, episode row model and menus as data The v3 pages change the library only through ShellServices.actions (src/services/library.ts), which carries the rules PodcastQueryGrid and spawnEpisodeContextMenu kept to themselves: private-feed interning, namesake refusal, setting membership instead of toggling it. Episode rows render one pure model (episodeRow.ts), and the episode and podcast menus are built as data that one renderer shows (menus.ts), anchored in the pointer's own document. Head.libraryActions becomes Head.action, so the wide Library gets Refresh all and a podcast's page its menu button. The legacy components still run beside these until the pages replace them. size: 100097 gzip (+1337 before the legacy deletions) * feat(ui): first run and Add podcast pages in the view First run is the empty library's page: the mark, "Add your first podcast", a focused "Search or paste a link" field, and quiet buttons to play a vault file or import OPML. Its first keystroke opens Add podcast, whose field sits in the head with the text and the caret kept. Results name each show's author and episode count; a pasted RSS URL or Apple Podcasts link resolves to one result with a chip naming its source. A tap adds the show and opens its episodes; an added show shows a check and opens. Failures read as fixed lines, never an error's text, and only the newest input may publish. Search podcasts leaves the settings tab: PodcastQueryGrid and PodcastResultCard are deleted, their rules live in LibraryActions and PodcastSearch, and their tests moved to AddPodcastPage.test.ts and library.test.ts. The settings tab and First run share pickFile, and the file menu and the vault media picker share playLocalFile. size: 98908 gzip (-1189 against the contracts commit) * feat(ui): Library grid with title tiles, error badges and the podcast menu The Library's grid is the shell's .pn-grid, sized by the view's own layout classes: 84 px tiles compact, 108 px wide, three columns on a phone, with no square backdrop behind rounded art. A tile whose artwork is missing or fails shows the podcast's title on a plain tile instead of initials or a blank square, and a podcast whose feed did not load wears a badge and says so in its label. Right-click or a long press opens the podcast menu. The wide Library's head refreshes every feed. PodcastGrid and PodcastGridCard are deleted; the Playlists page uses the same grid. size: 98703 gzip (-205) * feat(ui): podcast episodes page and one episode row for every list A podcast's page is headed by its artwork, title and author. A feed that did not load shows a "Feed didn't load" banner naming its server, never the address or the error, and keeps the cached episodes under it; Retry reloads, and a private feed whose address is not on this device offers "Set feed address" instead. The filter bar searches, hides played episodes and refreshes. Rows show played, in progress with the time left, playing or paused, unavailable, downloaded and has note, and say it in their label. Titles clamp at two lines and the More button sits beside them, never over them (#371). Tapping a row plays it and opens the player; the current episode only opens the player. More, right-click and long press open the episode menu. Latest, Queue, Local files, Played and a playlist render through the same page and row. The player's queue uses the row and the menus too. EpisodeList, EpisodeListItem, EpisodeListHeader, spawnEpisodeContextMenu and ImageLoader are deleted, with the shell's legacy scroller overrides; the libraryContext guard now allows only MainView. size: 98171 gzip (-532) * feat: open PodNotes once when the mobile app enables it for the first time A new install, a start that found no data.json, opens the view in the mobile app as it already does on desktop, so the empty view's search is the first thing a new user meets (#346). PodNotes writes data.json on that start, so later mobile starts stay dormant as before. size: 98200 gzip (+29) * refactor: drop the download check and unavailable reason no list reads The episode row decides "downloaded" from the library it is given, and an unavailable Played record says so in its row's label, so downloadedEpisodes.isEpisodeDownloaded and EpisodeListEntry's unavailableReason have no reader left. size: 98167 gzip (-33) * refactor(ui): share formatMinutes with the mini player The mini player and episode rows say time left the same way, from one function. Two comments that restated their code go. size: 98177 gzip (+10) * refactor(ui): play a vault file through the shared PickerModal The merge brought PickerModal from #442, so "Play a vault file" offers the vault's audio and video files through it instead of a second FuzzySuggestModal subclass. size: 99115 gzip (-38 against the merge, 99153; master e3243c4 is 99768) * perf(notes): read the vault once for a list's episode notes Episode rows ask whether their episode has a note, and since #442 that lookup reads every note's frontmatter for its guid. A podcast's page asks for all its rows at once, which read the vault once per episode. The notes with a guid are now read once per task and read again in the next, so a note created or synced since is still found. size: 99155 gzip (+40) * fix(ui): wrap long episode, result and card titles inside their buttons Obsidian's button style sets white-space: nowrap, which episode rows, search results and grid cards inherited, so a long title stayed on one clipped line instead of taking the two-line clamp. Live at 320 px the #213 Field Notes title now takes two lines ending in an ellipsis beside More options. npm run size: 99,109 gzip (master e3243c4: 99,768). * fix(ui): report a podcast's feed failure only in its page's banner The banner names the failure, keeps the cached episodes and offers Retry or Set feed address, so the podcast page no longer also raises the old "Could not load episodes" and private-address Notices. * fix(ui): keep Back on the wide Add podcast page reached from First run From the rail the page has nothing before it and stays without Back. * fix(ui): leave Copy episode link out when its target carries a feed secret An episode opened through an obsidian://podnotes URI for a private feed that is not subscribed carries the feed's own credential-bearing address, which the episode link embeds. The menu now offers no link for such an episode. A subscribed private feed's link keeps its placeholder, and the URI format is unchanged. npm run size: 99,118 gzip (master e3243c4: 99,768). * fix(ui): count an episode as current only when its media is the player's Two episodes of one feed that share a title share a key, so after one played, tapping the other only opened the player and both rows showed as playing. A row, Play and Open player now count an episode as current only when it has the player's key and the same media, as the player's own reuse rule does. The queue keeps its key identity. npm run size: 99,135 gzip (master e3243c4: 99,768). * fix(ui): offer only Mark as played for a Played record that left every feed The placeholder for such a record has no media or link, so Create note and Add to Favorites made a note and a favorite of nothing playable. As on master, its menu now holds only Mark as played or unplayed. Leaving early for it also drops the menu's other availability checks. npm run size: 99,135 gzip (master e3243c4: 99,768). * fix(ui): keep the row note icon current, and cheap enough for every row Each row asked getPodcastNote, whose title-overlap scan reads the whole notes folder for every row without a note, on mount and on each filter keystroke, and the icon never changed after a note was created or deleted. Rows now ask findExactPodcastNote, the guid index and the template's paths only, through a lookup the view replaces when a note is created, deleted or renamed. The view registers those vault events once the layout is ready, since the vault reports every file as created while it loads, and Obsidian removes them when the view closes. Edits do not refresh rows. The menu and opening a note keep the full lookup. npm run size: 99,219 gzip (master e3243c4: 99,768). * test(e2e): drive the journeys through the new Library, Add podcast and podcast pages Podcasts are added on the view's Add podcast page and removed from the podcast menu, Set feed address lives in the podcast page's banner and menu, and the episode menu uses the new labels. A journey that waited on the old Updating banner waits on feed status instead, the offline failure is asserted through the banner, the Library scroll journey has enough podcasts to scroll the denser grid, and the restored current episode's row opens the player, where play resumes it. * test(e2e): first run to playing, pasted feed and Apple links, and a feed error with Retry The fixture server can fail a feed until it is restored, and the iTunes stub answers lookups, so an Apple Podcasts link resolves offline. * test(e2e): the episode menu's order, playlist submenu, Mark as played and Open player * test(e2e): a new install on mobile opens PodNotes once, on First run J-ONB-02 splits into a true new install, with no data.json and no PodNotes leaf, and a later mobile start, which stays closed. * fix(ui): size the text cover's padding against the cover, not its row A percentage padding resolves against the containing block, so a 44 px result cover in a 500 px row took about 48 px of padding a side, grew to 97 px and lost its title. The padding is now 10cqw on the title, the cover being its inline-size container, and the title sits left as in the prototype. Rendered in headless Chrome with Obsidian 1.14.4's app.css at a 500 px view: grid card 94x94, result row 44x44, hero 64x64 and episode row 40x40, each showing its title (before: 97x97, 100x100 and 95x95 with a 0x0 title). The title is now 12% of the cover, near the prototype's 12.5cqw, so a grid card's reads 11.2 px, not 9 px. npm run size: 99,221 gzip (master e3243c4: 99,768). * docs(verify): drive feeds through the view's pages and add a new-install recipe Adding a podcast happens on the view's Add podcast page, removing it in the podcast menu, and Set feed address in the podcast page's banner or menu. The New install recipe disables PodNotes, deletes its data.json and enables it again, optionally under mobile emulation. * fix(ui): list a Played search's best match first The Played page filtered its own order by the search's results, so a search for "reading" listed a newer "Reading slowly, again and again" above "Reading". It now lists the search's results in their order, as master's filterPlayedEntries did. npm run size: 99,237 gzip (master e3243c4: 99,768). * fix(ui): leave played episodes out of the player's queue while hidden Master's queue in the full player followed the hide played preference, as every list did. The rows that replaced it listed every queued episode. The queue now reads the preference from the library context. npm run size: 99,257 gzip (master e3243c4: 99,768). * test(ui): move a queued episode up from the player's queue menu The test rendered the player over untyped services with no actions and only looked for the item's title, so it passed with a reorder that did nothing. It now uses the test services, chooses Move up in queue and checks the queue's order. npm run size: 99,257 gzip (master e3243c4: 99,768); tests only. * fix(services): say a private feed failed to load without its error's text Set feed address showed "Could not load feed: " and the error's message, which can carry the address being set, a secret. It now shows one fixed line, "Could not load the feed at that address." npm run size: 99,240 gzip (master e3243c4: 99,768). * fix(main): log a failure to open the view on a new install's first mobile start The first mobile enable opened the view with no rejection handler, so a failure became an unhandled rejection. It is now logged as the desktop startup path logs it. A new test starts on mobile with no data.json, then starts again on the data.json that first start wrote, and proves the view opens only the first time. npm run size: 99,244 gzip (master e3243c4: 99,768). * fix(ui): start First run over on Back, and open Add podcast only on text Back from Add podcast to First run showed the old query, and a search still in flight could publish its results. First run now clears the search when it opens. Spaces alone on First run no longer open an idle Add podcast page. npm run size: 99,260 gzip (master e3243c4: 99,768). * fix(ui): list nothing on a podcast or playlist page whose list is gone A podcast page whose feed was removed, or a playlist page whose playlist was, fell back to Latest episodes until the shell left the page. Each page now lists only its own episodes, and none once they are gone. npm run size: 99,269 gzip (master e3243c4: 99,768). * fix(ui): keep the full player's artwork square on mobile The artwork is the full player's play and pause button. It was a square made of padding-bottom, which Obsidian's mobile button padding overrides, so on a tablet or phone it collapsed to an 8 px strip. An aspect-ratio keeps it square whatever the button's padding. npm run size: 99,111 gzip (master e3243c4: 99,768). * fix(ui): refresh a restored view's note icons at layout ready, once per burst A view restored at startup lists its rows before Obsidian has indexed the vault, and the rows only looked their notes up again on a later vault event, so a note found by its GUID kept a missing icon for a quiet session. The view now registers its vault listeners when it mounts, so closing it removes them even before the layout is ready, ignores the files Obsidian reports while it loads the vault, refreshes the rows once at layout ready, and collapses a burst of changes into one lookup after 200 ms of quiet. The test mock's Component and ItemView follow Obsidian's lifecycle: open loads before onOpen, close unloads before onClose, and a callback registered after unload never runs. Size: main.js gzip 99,302 bytes (was 99,271), raw 312,785. * docs(verify): wait for the Add result that a pasted feed shows * test(e2e): J-LIST-04 waits for the Loading episodes line to leave both pages * test(e2e): J-FEED-05 removes a podcast on another device while Latest Episodes stays open * test(e2e): Open player on the playing episode keeps it playing and advancing * test(ui): iterate the fake vault's listeners without copying them * refactor: drop comments that restate the code they sit on Keeps only the whys the code cannot show: security, races, platform behavior and interface contracts. * fix(ui): drop a pending note lookup when the view closes, and name the result's episode count size: 99,317 gzip * test(ui): a same-titled episode without a downloaded copy shows as not downloaded * fix(ui): call an episode downloaded only when playback would play its copy A row and its menu matched a download by podcast and title, so a same-titled episode of the same podcast showed the other's download, and its Remove download would delete that file, while playback streamed it. The row, the menu and playback now share one matcher, downloadedCopy. * test(ui): listed episodes look their notes up again when the note path setting changes * fix(ui): show a listed episode's note once the note path setting points at it Rows looked their notes up again only for vault changes, so after a change to Note creation file path a row kept its old state while its menu offered Open note. The field now announces the change through the plugin store, as the queue toggle does, and the view looks again when the note path changes. The deferred-view test now opens and closes its view through load and unload, as Obsidian does. * test(ui): the search takes focus when its view first shows, not while it is hidden * fix(ui): focus the search field once its view is shown On a new mobile install the view mounted inside a hidden leaf, so the field's focus call failed and typing did nothing until a tap. An autofocus field now takes focus the first time it has a width, once. * test(ui): build the note-path test's note as Obsidian's TFile type allows * test(e2e): onboarding types into First run without tapping the field first * test(e2e): onboarding waits for the search field's focus before typing Typing came right after First run showed, so it could reach the window before the field took focus on the next frame. The journeys now wait until the field has focus, as a user waits for the caret, and fail by name when it never does. * test(e2e): write the mobile new-install eval's plugin values with jsLiteral * test(e2e): find a Back button by its label anywhere in the view The head's Back is .pn-back, or .pn-iconbtn on the Add podcast page, and the harness probe sits outside the head; the label alone names it.
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 #349. Fixes #351. Refs #347: this is the logic half; the player buttons belong to UI2.
Notes and links survive a retitle (#349). Episodes now carry the feed item's GUID, which is a lookup hint, not a storage key. New links get an optional
guid=parameter, through{{linktime}},{{linksegment}}and{{episodelink}}. New episode notes get quotedguidandfeedUrlfrontmatter. A link, an episode note, or a published transcript is found by GUID first, within its own feed. A GUID that several items in one feed share is never attached to those episodes, and a link whose GUID matches several items is resolved by title. In both cases the existing title rules apply, so old links and notes keep working. Identity stays podcast name plus title (getEpisodeKey), with nothing rekeyed.Create an episode note without playing (#351). With nothing loaded, Create episode note opens a podcast picker, then an episode picker, and creates the note from the template without touching the player. With an episode loaded it behaves as before.
Capture lands where you write (#347, logic). Capture Timestamp, Capture Last 10/20 Seconds and the headset's previous-track button now share one rule:
The three commands now run from anywhere, including while the PodNotes view has focus. The mini player's Capture timestamp button from #434 goes through the same
CaptureServiceand keeps its "Timestamp added to " notice.CaptureServicereturns{ destination, path, text, created }for UI2's toast. Until UI2 lands, the commands show no notice on success, as today.Also folded in from TRANS1:
formatEpisodeTimestampnow formats segment links as well, so the API no longer builds its own link.guid=link plays the renamed episode at its time#349, the episode menu after the feed renames the episode, master on the left:

#349, clicking a timestamp link captured before the rename, with another episode loaded:

screen recording
#347, the palette with the PodNotes view focused, master on the left:


screen recording
#351, nothing played, master on the left:


#347 on the v3 shell: the mini player's Timestamp button with focus in the PodNotes view puts the link at the caret of the note used last:

Verification
The checks ran at head d2ed715 (master 4cc2aeb merged in) with Node v22.23.2:
npm run lint: "Svelte policies: 0 errors, 0 warnings."npm run format:check: "All matched files use the correct format."npm run typecheck: exit 0.npm run build: exit 0.npm run test: 114 files, 2130 tests passed.npm run size: 99,739 bytes gzip against 98,763 on master 4cc2aeb (+976), and 314,656 bytes raw (+2,379). 261 bytes of the 100,000 budget remain.privateFeedUrlchange:npm run test:tooling: 11 tests passed.npm run docs:build: "All 112 legacy fragments resolve."npm run test:e2e: 15 files, 101 tests passed on Obsidian 1.14.4 (Linux, headless), on a fresh vault.The new e2e journeys, each checked against a planted defect:
guid=timestamp link and its Resume link. An old link without a GUID still plays its own episode.guidandfeedUrlin the same mapping, and Obsidian reads all its properties.Live proof in Obsidian 1.14.4, Linux, on an isolated vault, master and this PR:
http://podnotes.test:20644/feed.xml, with GUIDspodnotes-fixture-1andpodnotes-fixture-2.node .claude/skills/verify/fixtures.mjs retitleserves the two episodes as "Renamed Opening Show" and "Renamed Closing Show" with the same GUIDs. The cache was then cleared with Settings, Clear cache.capture.timestamp()that the unit tests cover throughbindMediaSession.Release impact
guid:currentEpisode, queue, playlists, favorites, local files and downloads. Nothing migrates.tests/fixtures/persistence/released-2.21.1.jsondecodes with no warnings in a unit test. Loaded into this build in real Obsidian, it leftdata.jsonbyte-identical and showed no notice or error.decodeEpisodecopies unknown fields (copySafeObject), soguidsurvives a downgrade.obsidian://podnotesgains an optionalguidparameter. Old links resolve as before. 2.21.1 reads parameters by name and ignoresguid.IAPIsignatures are unchanged.getPodcastTimeFormattedandgetPodcastSegmentFormattedlinks now carryguid=when the episode has one.guidandfeedUrlproperties when the episode has a GUID.feedUrlis its placeholder, never its address.{{episodelink}}and{{url}}in the default template already write the feed URL into notes.yaml2.8.4, the parser Obsidian bundles and already in the lockfile through vite, is now an explicit devDependency so the unit tests parse frontmatter the way Obsidian does. It is not inmain.js.Docs at release
Per standing order 28, these docs pages describe the released behavior and change at release:
docs/src/content/docs/commands.md:docs/src/content/docs/timestamps.md:guidfinds a renamed episode.guid, optional, the feed item's GUID, tried beforeepisodeName.docs/src/content/docs/notes.md:guidandfeedUrlproperties are found after a rename or move.docs/src/content/docs/mobile.mdlines 27 to 32 and 47 to 50: no note needs to be open to add the commands, and the headset follows the same rule.docs/src/content/docs/faq.mdlines 21 and 69: capture no longer needs a note open, and it goes to the editor used last.docs/src/content/docs/getting-started.mdline 42: no episode needs to be loaded.Review
An adversarial review ran in three rounds, with Claude Fable 5.1 and GPT 6.0 Astra.
The verdicts with reasons, including what was dismissed, are in the thread's audit notes.
Note
Identify episodes and notes by GUID, capture to last focused editor, create notes without playing
encodePodnotesURIandbuildEpisodeResumeLinkinclude the GUID, andpodNotesURIHandlerselects episodes by GUID (falling back to title) so links keep working after publishers rename episodes.CaptureService(CaptureService.ts) handles timestamp and segment capture. It writes to the focused Markdown editor, falls back to the last focused source-mode editor, and otherwise appends to the episode note. Capture commands are now plain callbacks and can run without an open editor; Reading view counts as no editor.pickEpisodeForNote(commands.ts) opens a saved-feed picker, loads the feed, and offers an episode picker.guidand feed URL frontmatter viaaddFrontmatterFields(noteFrontmatter.ts) andgetPodcastNoteresolves existing notes by GUID (scoped to the same feed) before title/path lookup. Duplicate GUIDs in a feed are stripped byFeedRepositoryso they cannot misidentify episodes.episodeMetadatareturns transcripts only and no longer matches GUIDs across feeds;FeedSuggestModalis replaced by the genericPickerModal; add-on lockfile now includes theyamldependency.Macroscope summarized 62c16e5.