Repository navigation
Conversation
Each test passes on the hand-written code. Together they catch wrong derivations that no existing test sees: the -1 SecretStorage ID suffix that released versions accept, the provider an export error names, the kind a settings key row adopts a secret for, and the key migration notices.
CREDENTIALS in src/types/Credentials.ts holds each kind's settings field, export key and label. The credential repository, settings decoder, settings transfer, settings tab and key migration notice loop over it. IPodNotesSettings takes its key fields from it, so a kind with no default in DEFAULT_SETTINGS fails typecheck. The plaintext key map from before 2.19.0 stays fixed.
… feat/transcription-providers
A provider's name was written twice: once in its credential row and once in its provider file. The row now owns it, so a provider file holds only its models and its request. Each model gains the name the settings will show.
The provider was derived from the speaker settings: Deepgram with speaker labels on, OpenAI otherwise. It is now stored as transcript.provider, with transcript.model beside it, where "" follows the provider's recommended model. schemaVersion stays 2 and both fields are additions, so a device on 2.24.0 keeps loading the library and keeps the fields on its own saves. A library that has no transcript.provider gets the provider it ran with: Deepgram for speaker labels with Deepgram, with or without its key, and OpenAI otherwise. The migration reads only the stored document, adds no decode warning, and gives the same answer on every load until a save stores it. transcript.diarization.provider belongs to versions up to 2.24.0. This version reads it for that migration alone, never writes it, keeps it in data.json as found, and leaves it out of settings exports. Importing a 2.24.0 export that labels speakers with Deepgram selects Deepgram. A provider or model ID this version does not have is kept as stored and shown under that name. An unknown provider refuses to transcribe with a notice. An unknown model runs the recommended model and the notice says so. The Transcripts settings now read: Transcription provider, the chosen provider's API key, Model, Speaker labels, Speaker label format, then the path and template. Speaker labels is disabled for a model that cannot label speakers. The Diarization provider row is gone.
ElevenLabs is a credential row, one provider file and a registry line. Its key reference is the new root field elevenLabsSecretId, stored in SecretStorage as podnotes-elevenlabs-api-key, and a settings export carries the key as secrets.elevenLabs. The export envelope stays at version 2. A public episode goes to ElevenLabs by its address, so nothing is downloaded. Any other episode is uploaded in one request. Word times and speakers reach the note, and turning speaker labels off asks for none. ElevenLabs answers 401 for a used-up quota as for a rejected key, and 400 for a model the key may not use as for an address it could not download, so the provider reads detail.code. Only the last of these is followed by an upload. A request waits 10 minutes for its answer. A new install starts on ElevenLabs with speaker labels on. A library saved up to 2.24.0 that holds no OpenAI key and has speaker labels off moves to ElevenLabs too, because it never set transcription up. A library that never stored the speaker labels choice keeps them off. The notice for a model without speaker labels now names ElevenLabs first.
A journey now seeds its transcription provider with its key, because a new vault is on ElevenLabs. The OpenAI journeys choose OpenAI. A new journey runs the default: a public episode goes to ElevenLabs by its address, no audio is fetched, and the note has timestamps and speaker labels. The ElevenLabs stub answers with the words ElevenLabs returns and records what each request named. The settings tab test walks the new Transcripts rows, and the key recovery test links the ElevenLabs key, the one a new vault shows.
The fixture provider answers api.elevenlabs.io with ElevenLabs' words, two speakers with the second 12 s in, and logs the address a request named in place of a file. The transcript guide follows the new settings rows and adds the ElevenLabs runs and a 2.24.0 library check.
…choice The provider row decided for itself whether the stored provider is one this version has, and the Speaker label format row read the stored speaker setting. Both now ask transcriptionChoice, as the key rows, the model row and the Speaker labels row do. The engine no longer exports a type nothing imports.
The Transcripts tests looked each control up by a CSS selector inside its row and asserted on a hidden row's inline style. They now query by role and by the row's name, as a user or a screen reader finds a control, so a hidden row is one the query does not find.
…ings measure The transcription engine left main.js at 103,795 gzip bytes. The provider and model settings, their migration and the ElevenLabs provider add 866, which takes it to 104,661 and over the 104,000 budget. The budget rises by those 866 bytes to 104,866, so the 205 bytes of headroom stay as they were.
…m library 2.24.0 imports the diarization object whole and fills a missing provider with OpenAI, so omitting the field moved Deepgram libraries to OpenAI. Imports here keep the provider this library stored.
A library migrated with OpenAI speaker labels on could not turn them off, so every transcription repeated the notice.
Deploying podnotes with
|
| Latest commit: |
1eef3e6
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://443218dd.podnotes.pages.dev |
| Branch Preview URL: | https://feat-transcription-providers.podnotes.pages.dev |
chhoumann
marked this pull request as ready for review
November 14, 2026 14:49
This branch has not been deployed
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
PodNotes is moving transcription to current models from several providers, so that a provider is a credential row, one provider file and a registry line, and no user stays on a model OpenAI retires. This PR is step 3 of that plan. A user now chooses the provider and the model in settings, and ElevenLabs Scribe v3 is the default. It stacks on #538.
The diff includes #537, which is merged into this branch, until #537 lands.
Transcription provider, the chosen provider's API key,Model,Speaker labels,Speaker label format, then the path and template. Keys linked for other providers stay stored.Speaker labelsis disabled for a model that cannot label speakers, and the row says so.Diarization provideris gone.Recommended (<name>)follows the provider's first model, so a later release can move everyone who never picked one.scribe_v3gets the address of an episode that a saved public feed lists, so PodNotes downloads and uploads nothing. Any other episode is uploaded in one request. The note has a timestamp on each paragraph and numbered speakers.Not in this PR: OpenRouter, merged speaker labels across parts, and the docs, which describe the released version until the release.
Verification
Run on Linux with Node 22.23.4, on the final tree:
npm run lint,npm run format:check,npm run typecheck,npm run buildandnpm run docs:buildpass.npm run test: 124 files, 2,857 tests pass.npm run test:tooling: 8 files, 48 tests pass.npm run test:e2eon Obsidian 1.14.4: 33 files, 255 tests pass.PODNOTES_OBSIDIAN_APP=/opt/Obsidian/obsidian npm run test:e2e -- settings-tab journey-resilienceon Obsidian 1.13.7: 2 files, 21 tests pass.npm run plant: 52 planted defects, each caught by the unit test named below, and 2 more caught bynpm run test:e2e.Obsidian 1.14.4 on Linux under Xvfb, in this worktree's isolated vault, with the verify skill's fixture provider answering the three providers. No real key was used. The fixture feed is
http://podnotes.test:28062/feed.xmland the private one is atpriv3.supercast.com.data.jsonthen held the provider withmodel: "".Transcribe current episode from audiosent one request:POST https://api.elevenlabs.io/v1/speech-to-text, 604 bytes, naminghttp://podnotes.test:28062/episode-2.wav. The fixture server saw no request during the command. The note has**1:**at 0 s and**2:**at 12 s.urlSecretId. The command sentGET /p/episode-1.wav, then onePOSTof 960,649 bytes with no address in it.main.jswrote two libraries through its own settings tab. Its local build has the SHA-256 of the release asset,b7ba1dfe...0530484. This build loaded speaker labels with Deepgram, no Deepgram key and a linked OpenAI key as Deepgram, asked for a Deepgram key, and sent nothing. It loaded speaker labels with OpenAI as OpenAI, showed the disabledSpeaker labelsrow, saved the transcript without labels, and the notice was still up 20 s later.data.jsonwas byte-identical after each load.transcript.diarization.provider: "openai", which 2.24.0 adds for itself.transcript.provider,transcript.modelandelevenLabsSecretIdwere kept. This build then read its fields back unchanged.dev:errorsreported no error after each step. The lead verifies with real keys separately.Planted defects, each caught by the named test through
npm run plantsettingsMigrations.tssettingsMigrations.ts,persistence/settingsCodec.tssettingsMigrations.tssettingsMigrations.tssettingsMigrations.tssettingsMigrations.tspersistence/settingsCodec.tspersistence/settingsCodec.tspersistence/settingsCodec.tspersistence/settingsCodec.tssettingsMigrations.tssettingsMigrations.tsconstants.tsconstants.tsconstants.tssettingsTransfer.tssettingsTransfer.tssettingsTransfer.tssettingsTransfer.tssettingsTransfer.tssettingsTransfer.tsservices/transcription/registry.tsservices/TranscriptionService.tsservices/transcription/registry.tsservices/transcription/transcribe.tsservices/transcription/registry.tsservices/transcription/providers/deepgram.tsservices/transcription/cues.tsservices/transcription/providers/elevenlabs.tsservices/transcription/transcribe.tsservices/transcription/providers/elevenlabs.tsservices/transcription/providers/elevenlabs.tsservices/transcription/providers/elevenlabs.tsservices/transcription/providers/elevenlabs.tsservices/transcription/providers/elevenlabs.tsservices/transcription/providers/elevenlabs.tsservices/transcription/providers/elevenlabs.tsservices/transcription/providers/elevenlabs.tsservices/transcription/providers/elevenlabs.tsservices/transcription/providers/elevenlabs.tsui/settings/PodNotesSettingsTab.tsui/settings/PodNotesSettingsTab.tsui/settings/PodNotesSettingsTab.tsui/settings/PodNotesSettingsTab.tsui/settings/PodNotesSettingsTab.tsui/settings/PodNotesSettingsTab.tsui/settings/PodNotesSettingsTab.tsui/settings/PodNotesSettingsTab.tsui/settings/PodNotesSettingsTab.tsui/settings/PodNotesSettingsTab.tsconstants.tssettingsMigrations.tsservices/transcription/providers/elevenlabs.tsui/settings/PodNotesSettingsTab.tsRenaming the ElevenLabs row's
settingsKeyorexportKeyfailsnpm run typecheck, inconstants.tsand in the tests that passelevenLabs, so no plant covers those names.Real ElevenLabs key (lead, after review)
Obsidian 1.14.4, Linux, build of
1eef3e6f, palette command "Transcribe current episode from audio", speaker labels on, a pass-through request log (no fixture):https://feeds.npr.org/500005/podcast.xml: one 929-byte POST toapi.elevenlabs.io/v1/speech-to-textcarryingsource_url, no enclosure download. Saved in 5 s with timestamp links and speakers 1 to 4.dev:errors: none. Free-tier credits limited this to short audio; the lab measured a 54 min episode in about 30 s.Review fixes (lead)
diarization.provideris now"deepgram"exactly when the library uses Deepgram, else"openai", because 2.24.0 imports the object whole and fills a missing provider with OpenAI. Importing here copies onlyenabledandspeakerTemplate, so the stored field stays as the library wrote it. Plants: omit the provider (caught byexports the provider and model, and gives versions up to 2.24.0 Deepgram only for a Deepgram library); spread the file's object again (caught byimporting this version's export leaves the provider field of versions up to 2.24.0 as the library stored it).Speaker labels can only be turned off for a model without them, and the row says why).Release impact
Storage.
schemaVersionstays 2. The first save addstranscript.provider,transcript.modelandelevenLabsSecretId. Nothing else in a 2.24.0data.jsonchanges, and the load itself writes nothing.Migration, when
transcript.provideris absent. It reads only the stored document, adds no decode warning, and gives the same answer on every load until a save stores it.gpt-transcribe, as on #538.Every row gets
model: "". A library that never stored the speaker labels choice keeps them off. A library from before 2.19.0 that holds its OpenAI key as text counts as holding an OpenAI key.Sync with 2.24.0. A device on 2.24.0 keeps loading the library and keeps the three new fields on its own saves. It transcribes as before, from
transcript.diarization, and writestranscript.diarization.providerthere. This version reads that field only for the migration, never writes it, and keeps it as found. A provider chosen here does not reach a 2.24.0 device, and a change toDiarization providerthere does not reach this version oncetranscript.provideris stored.Settings export. The envelope stays at version 2.
secretsgainselevenLabs, andsettings.transcriptcarriesproviderandmodelwithoutdiarization.provider. Importing a 2.24.0 export that labels speakers with Deepgram selects Deepgram. Any other 2.24.0 export keeps the provider. An import leaves the storeddiarization.provideralone.Leaving
diarization.providerout does not protect a 2.24.0 importer. Run against the 2.24.0 source, its import replaced the wholediarizationobject and filled the provider with"openai", with the field absent as with"openai"in the file. Only a file with nodiarizationobject left its Deepgram choice alone, and that file would not carry the speaker labels setting either. This needs a decision.Speaker labels with OpenAI. The row is disabled for OpenAI, so a library that arrives with speaker labels on cannot turn them off without first choosing another provider. Until then the notice shows after every transcription. This needs a decision too.
Bundle.
main.jsgrows from 318,278 to 320,988 raw bytes and from 103,795 to 104,661 gzip bytes, measured against the merge of #538 and #537. That is 866 gzip bytes over the engine, and 661 over the 104,000 budget. The budget rises by the measured 866 to 104,866, which keeps the 205 bytes of headroom.scripts/size-baseline.mdrecords it.ElevenLabs errors. The provider reads
detail.codeanddetail.messageas the lead measured them. The tests build those bodies from that description, not from a captured response.Note
Add transcription provider and model selection with ElevenLabs as default
diarizefrom this flag, and timed cues omit provider-supplied speaker names when labels are disabled in cues.tsdiarization.providerafter 2.24.0 must rely on the compatibility rewrite in the portable settings helperMacroscope summarized 1eef3e6.
Note
Medium Risk
Touches API keys, SecretStorage migration, settings import/export across versions, and paid third-party transcription flows—well-tested but user-facing and billing-sensitive.
Overview
Adds ElevenLabs as the default transcription backend and replaces the old speaker-diarization provider split with explicit
Transcription provider,Model, andSpeaker labelssettings (per-provider API key rows, recommended vs named models, unknown providers/models kept as stored).The transcription stack gains an ElevenLabs Scribe v3 implementation (URL-based transcription for public episodes, upload fallback for private/local audio), a unified credential registry including
elevenLabsSecretId, and load/save/import migration that maps pre-2.24.0diarizationchoices intotranscript.provider/model.TranscriptionServicenow blocks unsupported providers and missing keys up front; Deepgram/ElevenLabs honor the speaker-labels toggle.E2E/verify harness stubs ElevenLabs alongside OpenAI/Deepgram; gzip bundle budget rises to 104,866 bytes for the new UI and provider code.
Reviewed by Cursor Bugbot for commit 1eef3e6. Bugbot is set up for automated code reviews on this repo. Configure here.