Skip to content

feat(notes): ship a Bases-friendly default episode note template (#160)#201

Open
chhoumann wants to merge 5 commits into
masterfrom
chhoumann/160-bases-default-template
Open

feat(notes): ship a Bases-friendly default episode note template (#160)#201
chhoumann wants to merge 5 commits into
masterfrom
chhoumann/160-bases-default-template

Conversation

@chhoumann

Copy link
Copy Markdown
Owner

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 existing download.path convention).
  • note.template: YAML frontmatter with structured properties Bases can sort, filter, and group on, plus user-fillable tracking fields:
---
type: podcastEpisode
podcast: "{{podcastlink}}"
url: "{{url}}"
date: {{date:YYYY-MM-DD}}
tags:
  - podcastEpisode
status:
rating:
favorite: false
---
# {{title}}

![]({{artwork}})

[Resume in PodNotes]({{episodelink}})

{{description}}

{{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 - new note.path / note.template defaults (scoped to the note block only).
  • src/TemplateEngine.ts - NoteTemplateEngine now sanitizes the URL tags (url, stream, artwork, episodeurl, episodeartwork, feedurl, feedartwork) with the existing sanitizeUrlForTemplate (strips " and \), mirroring FeedNoteTemplateEngine. This is lossless for well-formed URLs and keeps a quoted scalar like url: "{{url}}" valid. Each tag is guarded with ?? "" so a corrupted data.json can't throw.
  • src/settingsMigrations.ts - new migrateNoteSettings, 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 - loadSettings applies migrateNoteSettings.
  • scripts/provision-obsidian-e2e-vault.mjs - the e2e seed note block now matches DEFAULT_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 .base view, and the URL-tag sanitization.

Compatibility / migration impact

Defaults only reach new installs and users who never configured note settings. Existing users are protected:

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:

npm run lint
npm run format:check
npm run typecheck
npm run build
npm run test        # 466 passing
npm run docs:build  # mkdocs --strict

Real-Obsidian verification in an isolated worktree vault (npm run obsidian:e2e): drove the actual create-podcast-note command 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:

  • hostile: 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.
  • no-date: 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 .base to the documented groupBy form (the canonical Bases schema has no sort view key), and qualifying the "valid YAML" claim for hand-edited feed-note paths that contain YAML-hostile characters.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 16, 2026

Copy link
Copy Markdown

Deploying podnotes with  Cloudflare Pages  Cloudflare Pages

Latest commit: b698643
Status: ✅  Deploy successful!
Preview URL: https://3458bb71.podnotes.pages.dev
Branch Preview URL: https://chhoumann-160-bases-default.podnotes.pages.dev

View logs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/TemplateEngine.ts Outdated
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.
@chhoumann chhoumann force-pushed the chhoumann/160-bases-default-template branch from c695f84 to 1d59e3c Compare June 16, 2026 10:06
@chhoumann

Copy link
Copy Markdown
Owner Author

@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?

@chhoumann

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/constants.ts Outdated
…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).
@chhoumann

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: b69864348f

ℹ️ 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".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FR: Add Obsidian Bases in default note template

1 participant