PodNotes is an Obsidian community plugin for listening to podcasts, tracking playback progress, creating podcast notes, capturing timestamps, downloading episodes, using local audio files, and exposing a small API for workflow plugins.
Source code lives in src/. Core plugin registration and lifecycle wiring live
in src/main.ts; public API code is under src/API/; parsing code is under
src/parser/; stores and controllers are under src/store*; utility functions
are under src/utility/; Svelte UI lives under src/ui/; shared types live in
src/types/.
Tests are colocated with source files as *.test.ts where practical. Shared
test mocks live in tests/mocks/. User-facing documentation lives in docs/
and is built with MkDocs.
Generated plugin artifacts such as main.js and source maps are ignored by git
and should not be hand-edited. Production builds write main.js at the repo
root for release packaging; development builds write into build/ and maintain
root symlinks for local Obsidian loading.
- Use Node 22. The repo has
.nvmrc,.npmrc, andpackage.jsonengines for this. - Use npm for package management and scripts. Do not introduce another package manager unless the migration is intentional and removes the old lockfile.
- Use Conventional Commits (
feat:,fix:,test:,docs:,chore:) so semantic-release can determine versions. - If work resolves a GitHub issue, prefer an issue-linked branch workflow before implementation.
npm install: install dependencies for local development.npm run dev: watch-mode development build via Vite.npm run typecheck: runtsc --noEmit.npm run lint: run ESLint against TypeScript sources.npm run format:check: run the configured Biome check.npm run check:a11y: runsvelte-check --fail-on-warnings.npm run test: run Svelte checks and the Vitest suite.npm run build: type-check and produce the production plugin bundle.npm run docs:build: build the MkDocs documentation.npm run docs:deploy: build docs and deploydocs/siteto Cloudflare Pages.
Before opening a PR or cutting a release, run the CI-equivalent checks locally:
npm run lint
npm run format:check
npm run typecheck
npm run build
npm run test
npm run docs:buildVitest runs in jsdom and aliases obsidian to tests/mocks/obsidian.ts.
Prefer unit tests for pure utility, parser, store, API, and component behavior.
Use Testing Library for Svelte component behavior instead of asserting on
implementation details.
When a bug depends on real Obsidian runtime behavior, reproduce it in Obsidian before changing code and verify it there after the fix. Timestamp links, URI handling, playback restore, downloaded/local media, file writes, settings migrations, and workspace/view behavior are runtime-sensitive and should not be trusted to jsdom alone.
For runtime verification, record the exact Obsidian version, platform, vault setup, feed or local file used, command or URI invoked, console/runtime errors, and observed plugin state before and after the action.
Use a dedicated development vault for manual or scripted Obsidian checks. Ensure the vault's PodNotes plugin folder points at this checkout's generated plugin artifacts before trusting runtime evidence.
If using the obsidian CLI, pass the vault selector consistently and prefer
scripted, repeatable checks for non-trivial flows. For bugs involving commands
or URIs, test both the user-facing path and the direct command/URI path when
possible.
For work in the canonical /Users/christian/Developer/PodNotes checkout, use the
shared dev vault and target it explicitly with the obsidian CLI:
npm run dev
# reload or re-enable PodNotes in the dev vault, e.g.:
obsidian vault=dev plugin:reload id=podnotes
# trigger the relevant command, UI flow, or obsidian://podnotes URI
obsidian vault=dev eval code='app.plugins.plugins.podnotes?.manifest?.version'
# inspect console/errors and plugin state- Dev vault root:
/Users/christian/Developer/dev_vault/dev. - PodNotes plugin folder in the vault:
/Users/christian/Developer/dev_vault/dev/.obsidian/plugins/podnotes, whosemain.js/manifest.jsonsymlinks point at the canonical checkout's artifacts. - Only one checkout can own those symlinks at a time, so the shared
devvault is for the main checkout. Worktrees must use the isolated wrapper below.
In a worktree (e.g. /Users/christian/orca/workspaces/PodNotes/<slug>), do not
race the shared dev vault — multiple worktree agents would clobber each other on
the plugin symlink, data.json, and plugin:reload. Use the isolated worktree
wrapper instead, which provisions a worktree-local vault under
.obsidian-e2e-vaults/podnotes-<worktree> (git-ignored), starts or reuses a
private-HOME Obsidian instance bound to that vault, disables Restricted Mode,
waits until PodNotes is live, and then runs your command with the right
vault=<worktree vault> and private HOME already applied:
npm run build # produce root main.js + manifest.json first
npm run obsidian:e2e -- eval code=app.vault.getName()
npm run obsidian:e2e -- eval code='Boolean(app.plugins.plugins.podnotes)'
npm run obsidian:e2e -- dev:errors-
The wrapper links the worktree's own
main.js/manifest.json(PodNotes injects its CSS into the bundle, so there is nostyles.cssto link) and seeds a cleanDEFAULT_SETTINGS-shapeddata.jsonon first provision; it never touches/Users/christian/Developer/dev_vault/dev. -
npm run provision:e2e-vaultandnpm run start:e2e-obsidianexpose the provision/launch steps individually; both accept--help. -
Use
npm run start:e2e-obsidian -- --print-envonly when you need to exportPODNOTES_E2E_VAULT/PODNOTES_E2E_VAULT_PATH/PODNOTES_E2E_OBSIDIAN_HOMEfor a separate process. TheobsidianCLI routes by$HOME(it talks to$HOME/.obsidian-cli.sock), so to point the Vitesttests/e2esuite at the isolated instance you must remapHOMEas well as the vault name — exportingPODNOTES_E2E_VAULTalone leaves the suite talking to the shareddevvault:npm run build # required: provisioning links main.js eval "$(npm run --silent start:e2e-obsidian -- --print-env)" export HOME="$PODNOTES_E2E_OBSIDIAN_HOME" # required: re-point the CLI socket PODNOTES_E2E_VAULT="$PODNOTES_E2E_VAULT" npm run test:e2e
Build first so the instance loads the current bundle (provisioning also needs
main.jsto exist).start:e2e-obsidianreloads PodNotes when it reuses a running instance, so the exported instance is never stale.
Each started instance is a real Obsidian process tree plus a private profile
directory under /private/tmp/podnotes-obsidian-e2e/<vault>-<hash>/. Removing a
worktree does not stop it, so a finished worktree would leak an Obsidian
process tree and a /private/tmp directory. Stop it explicitly:
npm run stop:e2e-obsidian # stop THIS worktree's instance + remove its tmp dir
npm run stop:e2e-obsidian -- --dry-run # show what would be stopped/removed
npm run stop:e2e-obsidian -- --prune # also reap orphaned instances (worktree gone)The teardown identifies only this worktree's instance by its private
--user-data-dir token (which contains a per-worktree hash), terminates that
process tree (SIGTERM, then SIGKILL for stragglers), and removes its profile
directory. It never touches the shared dev vault, other worktrees, or quickadd
instances.
Two layers keep instances from leaking, so you rarely need to run stop by hand:
- Orca archive hook —
orca.yamldefines ascripts.archivehook that runs this teardown for the worktree being removed. Remove worktrees withorca worktree rm --worktree <selector> --run-hooksso the hook fires (Orca skips archive hooks without--run-hooks). - Reap on next start —
start:e2e-obsidianandobsidian:e2ereap any orphaned instance (one whose backing worktree no longer exists on disk, i.e. it was removed) before launching, even if its Obsidian is still running. An idle instance for a worktree that still exists is left alone so concurrent workers can reuse it. Reaping scans the default profile root (/tmp/podnotes-obsidian-e2e); instances started under a custom--profile-rootare only reaped by a start that uses that same root, so stop those explicitly.
Docs live in docs/docs/ and are configured by docs/mkdocs.yml. Update docs
with user-facing behavior changes, new commands, API changes, template syntax,
transcript behavior, local-file behavior, or import/export changes.
Use npm run docs:build to validate docs locally. The Cloudflare Pages output
directory is docs/site, configured in wrangler.jsonc.
The release setup is semantic-release based. npm version runs
version-bump.mjs, which keeps manifest.json and versions.json in sync with
the package version and Obsidian minAppVersion.
Release assets are main.js and manifest.json. Keep version metadata changes
generated by the release process with the release commit, and treat unexpected
diffs in package.json, package-lock.json, manifest.json, or
versions.json as blockers until understood.
Pull requests should include:
- a concise summary of the user-facing change;
- linked issues when relevant;
- screenshots or short recordings for visible UI changes;
- feed URLs, local file details, or transcript setup for podcast-specific fixes;
- exact commands run and whether Obsidian runtime verification was performed;
- release or migration impact, especially for settings, storage, API, or URI behavior.
Keep changes scoped to the touched behavior. Do not mix unrelated formatting, dependency churn, docs rewrites, or generated artifact changes into feature and bug-fix commits.