feat(notes): ship a Bases-friendly default episode note template (#160) - #201
Conversation
Deploying podnotes with
|
| Latest commit: |
b698643
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://3458bb71.podnotes.pages.dev |
| Branch Preview URL: | https://chhoumann-160-bases-default.podnotes.pages.dev |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c695f8444a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The episode note path/template both defaulted to "" (empty), which left the
"Create episode note" command disabled on a fresh install and gave new users
no structured metadata. Ship sensible defaults:
- note.path: PodNotes/{{podcast}}/{{title}}.md (matches the download convention)
- note.template: Bases-friendly frontmatter (type/podcast-link/url/date/tags +
user-fillable status/rating/favorite), with the raw title as the body H1 so
YAML rules never apply to it. {{podcastlink}} ties each episode to its #163
feed note for Bases/Dataview rollups.
Make the frontmatter bulletproof: NoteTemplateEngine now sanitizes the URL tags
({{url}}/{{stream}}/{{artwork}}/{{episodeurl}}/{{episodeartwork}}/{{feedurl}}/
{{feedartwork}}) by stripping " and \\ (lossless for valid URLs), mirroring
FeedNoteTemplateEngine, so a quoted scalar like url: "{{url}}" stays valid.
Migrate existing users without clobbering customizations: migrateNotePath /
migrateNoteTemplate upgrade ONLY the exact legacy empty value (or absent), so
any non-empty path/template the user configured is preserved. Mirrors the #183
migrateDownloadPath precedent and is wired into loadSettings.
Update the e2e provisioning seed to match (keeps the seed/DEFAULT_SETTINGS drift
test green and seeds the new default for verification), refresh the settings
placeholder, and document the default + an example .base view in templates.md.
Verified in the isolated worktree Obsidian vault: notes created via the real
create-podcast-note command (incl. a hostile title/URL and a no-date episode)
parse into clean frontmatter via Obsidian's properties parser.
…review) Address ultracode review findings on the #160 default template: - NoteTemplateEngine: guard {{url}}/{{stream}}/{{episodeurl}} with `?? ""` before sanitizeUrlForTemplate, matching the other URL tags. A corrupted/hand-edited data.json could surface a null/undefined on these typed-string fields, where the unguarded `.replace` would throw and abort note creation. Add a regression test covering a corrupted episode. - docs/templates.md: the default puts {{artwork}} in the body as a Markdown image, not a quoted frontmatter scalar; correct the YAML-safety bullet to say so. - docs/templates.md: quote the example .base filter condition ('type == "podcastEpisode"') to match Obsidian's canonical Bases syntax.
…eview) Address findings from the adversarial review of the #160 default template: - Migration: only upgrade the legacy empty note when the WHOLE note is the old default (path AND template both empty/absent). Replace the per-field migrateNotePath/migrateNoteTemplate with migrateNoteSettings so a user who configured one field and deliberately left the other empty (e.g. a custom path with an empty template to keep "Create episode note" disabled) is never silently re-enabled. Coalesce null fields so a corrupted data.json can't reach the path/template engines. Update tests to cover the preserve-partial case. - docs/templates.md: the example .base used a `sort:` view key, which is not part of the documented Obsidian Bases schema (sorting is view UI state; only `order` + `groupBy` are documented). Switch to `groupBy` (by listening status) and `note.`-prefixed properties, matching the canonical syntax. - Qualify the "always valid YAML" claim (docs + constants comment): the guarantee holds for the default and any ordinary feed-note path; a feed-note path with a literal quote/backslash in a folder segment would flow into {{podcastlink}}.
…review)
Codex review (P2): globally stripping "/\ from NoteTemplateEngine's URL tags
corrupts {{url}} for local-file episodes, where episode.url is a wikilink from
generateMarkdownLink (e.g. a file named Talk "A".mp3 would link to Talk A.mp3),
and changes existing custom templates that use {{url}} outside YAML.
Revert the URL-tag sanitization added earlier in this branch back to master's
verbatim behavior. The default frontmatter stays valid YAML for the common case
without mutating tag values: feed episode URLs are well-formed (no quotes), the
{{podcastlink}} name is already sanitized, and ordinary local-file links contain
no quotes. A URL or file name with a literal quote is the one case that would
need adjusting, which is documented. sanitizeUrlForTemplate stays in use by
FeedNoteTemplateEngine, where {{url}} is always a real website URL.
Add a test guarding that {{url}}/{{episodeurl}} pass through verbatim (local-file
wikilinks preserved), and update the docs/comment claims accordingly.
c695f84 to
1d59e3c
Compare
|
@codex review Rebased onto master (resolved the src/constants.ts overlap with #114, keeping both the new episodeListLimit setting and the note.template default) and addressed your P2 by reverting the URL-tag sanitization to verbatim rendering in NoteTemplateEngine (commit 1d59e3c). Gates are green. Could you take another look? |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1d59e3c41a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…review)
Codex review (P2): after rendering URL tags verbatim, a local-file episode whose
name contains a double quote (e.g. Talk "A".mp3, playable on macOS/Linux) makes
{{url}} a wikilink with a quote, so url: "{{url}}" renders
url: "[[Talk "A".mp3]]" and the whole frontmatter fails to parse, hiding every
property from Bases.
Move {{url}} (and it was already true for {{artwork}}) into the note body, where
YAML rules don't apply, so the verbatim tag value can never break the
frontmatter. The frontmatter now contains only values that can't carry a
YAML-hostile character (type, the sanitized {{podcastlink}}, an ISO/empty date,
tags, and the user-fillable status/rating/favorite). Feed-only users can add
url: "{{url}}" back; documented. Update the example, the e2e seed, and the test
(now exercises a quote-bearing local-file url to prove the frontmatter stays
parseable).
|
@codex review |
|
Codex Review: Didn't find any major issues. 👍 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
# [2.17.0](2.16.0...2.17.0) (2026-06-22) ### Bug Fixes * behavioral-audit logic and robustness fixes (back-end, 1/2) ([#213](#213)) ([4e2845d](4e2845d)) * **download:** default per-episode download path and migrate empty default ([#183](#183)) ([#186](#186)) ([46a6486](46a6486)) * **download:** prevent Android crash and create missing folders on download ([#178](#178)) ([ecd09d5](ecd09d5)), closes [#113](#113) [#86](#86) [#113](#113) [#86](#86) * **lifecycle:** defer mobile podcast view startup ([#208](#208)) ([8bf9ce4](8bf9ce4)) * **notes:** cap note path length and harden folder creation ([#22](#22), [#87](#87)) ([#192](#192)) ([858e280](858e280)), closes [#87-class](#87) * **playback:** persist listened time during playback ([#33](#33)) ([#190](#190)) ([e3433c2](e3433c2)), closes [#191](#191) [#108](#108) [#163](#163) [#183](#183) * **playback:** play local files and downloads on iOS via resource path ([#100](#100)) ([#184](#184)) ([12c503a](12c503a)) * **player:** clear progress on episode switch to stop end-of-playback glitch ([#94](#94)) ([#194](#194)) ([59dccb3](59dccb3)) * **player:** reveal PodNotes view on Play with PodNotes so local files play ([#84](#84)) ([#198](#198)) ([5953625](5953625)) * **settings:** show labelled Add/Remove buttons in podcast search ([#109](#109)) ([#195](#195)) ([33399f5](33399f5)) * show downloaded episodes in the Local Files playlist ([#176](#176)) ([#177](#177)) ([184188c](184188c)) * **timestamps:** capture into the cursor's table cell without breaking the row ([#165](#165)) ([#203](#203)) ([964e342](964e342)) * **transcription:** always transcribe the currently playing episode ([#182](#182)) ([62b488a](62b488a)), closes [#107](#107) * **uri:** preserve '+' in episode titles and paths for timestamp links ([#181](#181)) ([8ad7aa5](8ad7aa5)), closes [#164](#164) * **view:** reliably reveal PodNotes view via command + ribbon icon ([#55](#55)) ([#199](#199)) ([fa1d708](fa1d708)) ### Features * add podcast segment links ([#205](#205)) ([d97e59e](d97e59e)) * **api:** expose generated episode transcripts ([e264465](e264465)), closes [#105](#105) * behavioral-audit UI and interaction fixes (front-end, 2/2) ([#215](#215)) ([894d93d](894d93d)) * **commands:** add playback rate and media timestamp controls ([#206](#206)) ([a8bb44a](a8bb44a)) * **devx:** isolated per-worktree Obsidian E2E vault wrapper ([#188](#188)) ([a8b7a4a](a8b7a4a)) * **episodes:** add a setting to control the Latest Episodes list length ([#114](#114)) ([#200](#200)) ([7b3e3c6](7b3e3c6)) * **notes:** add {{episodelink}} template tag to resume an episode from its note ([#35](#35)) ([#193](#193)) ([8c1ddd6](8c1ddd6)) * **notes:** add podcast feed-level notes ([#163](#163)) ([#187](#187)) ([db0de47](db0de47)), closes [#161](#161) [#160](#160) * **notes:** ship a Bases-friendly default episode note template ([#160](#160)) ([#201](#201)) ([209431d](209431d)), closes [#163](#163) [#183](#183) * **player:** scale episode title font size to its length ([#81](#81)) ([#202](#202)) ([659b6b8](659b6b8)) * **player:** support video episode playback ([#209](#209)) ([f92a91f](f92a91f)) * **queue:** add setting to disable queue auto-population and auto-advance ([#108](#108)) ([#185](#185)) ([cf3d73c](cf3d73c)) * **queue:** allow reordering the playback queue ([#80](#80)) ([#179](#179)) ([d994d61](d994d61)), closes [#173](#173) * **settings:** import/export settings & templates ([#180](#180)) ([a27d23d](a27d23d)), closes [#162](#162) [#162](#162) * **templates:** add {{currentDate}}, {{episodeNumber}}, {{duration}} template variables ([#189](#189)) ([ec573eb](ec573eb)), closes [#75](#75) [#34](#34) [#88](#88) [163/#186](#186) * **templates:** add episode chapters tag ([#207](#207)) ([9c98863](9c98863)) * **transcripts:** opt-in speaker diarization for transcripts ([#168](#168)) ([#204](#204)) ([a96e12f](a96e12f))
|
🎉 This PR is included in version 2.17.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
Closes #160.
The episode note defaults shipped empty (
note.path = "",note.template = ""), which left the "Create episode note" command disabled on a fresh install and gave new users no structured metadata to build Obsidian Bases views on. This PR ships a sensible, Bases-friendly default for both:note.path:PodNotes/{{podcast}}/{{title}}.md(matches the existingdownload.pathconvention).note.template: YAML frontmatter with structured properties Bases can sort, filter, and group on, plus user-fillable tracking fields:{{podcastlink}}ties each episode to its podcast feed note (the #163 feature) so Bases / Dataview can roll episodes up under their show. The raw{{title}}lives in the body H1, where YAML rules do not apply, so awkward titles never break the frontmatter.What changed
src/constants.ts- newnote.path/note.templatedefaults (scoped to thenoteblock only).src/TemplateEngine.ts-NoteTemplateEnginenow sanitizes the URL tags (url,stream,artwork,episodeurl,episodeartwork,feedurl,feedartwork) with the existingsanitizeUrlForTemplate(strips"and\), mirroringFeedNoteTemplateEngine. This is lossless for well-formed URLs and keeps a quoted scalar likeurl: "{{url}}"valid. Each tag is guarded with?? ""so a corrupteddata.jsoncan't throw.src/settingsMigrations.ts- newmigrateNoteSettings, which upgrades the legacy empty note to the new default only when the whole note is the old default (both path and template empty/absent). Any configured field is preserved verbatim, including a deliberately-empty field used to keep note creation disabled.src/main.ts-loadSettingsappliesmigrateNoteSettings.scripts/provision-obsidian-e2e-vault.mjs- the e2e seed note block now matchesDEFAULT_SETTINGS(keeps the seed/DEFAULT_SETTINGS drift test green and seeds the new default for verification).src/ui/settings/PodNotesSettingsTab.ts- the note-template placeholder now shows a Bases-friendly frontmatter hint instead of the old Dataview::style.docs/docs/templates.md- documents the new default, an example.baseview, and the URL-tag sanitization.Compatibility / migration impact
Defaults only reach new installs and users who never configured note settings. Existing users are protected:
{ path: "", template: "" }). A user who customized either field keeps both fields untouched.migrateDownloadPath).The URL-tag sanitization changes existing tag output only for malformed inputs (a
"or\in a URL, which valid URLs never contain). For local-file episodes{{url}}is a wikilink; a normal filename is unaffected, and a filename containing a literal"would have its quote stripped (rare; valid YAML is preferred over an exact link in that edge case).Verification
Gates (Node 22), all green:
Real-Obsidian verification in an isolated worktree vault (
npm run obsidian:e2e): drove the actualcreate-podcast-notecommand for several episodes, including a hostile title/URL (Why "AI": a deep dive: part 2, URL with an embedded quote and backslash) and a no-date episode. Obsidian's properties parser produced clean frontmatter in every case:url: "https://example.com/ep?x=qc",podcast: "[[PodNotes/Podcasts/My Show|My Show]]",tags: [podcastEpisode],status: null,rating: null,favorite: false; the raw title sits only in the body H1.date: null.Review
Ran a multi-dimension self-review plus two adversarial reviewers. Key fixes that came out of it: the whole-note migration gate (so an intentional empty field is never re-enabled), correcting the example
.baseto the documentedgroupByform (the canonical Bases schema has nosortview key), and qualifying the "valid YAML" claim for hand-edited feed-note paths that contain YAML-hostile characters.