Skip to content

v0.5.1

Latest

Choose a tag to compare

@github-actions github-actions released this 21 May 15:42
· 225 commits to main since this release

TL;DR

First stable release since v0.4.1 (4 weeks, 372 commits). Headline changes:

  • Teams 2.x silent recordings fixed — multi-PID audio tap captures all Electron helper processes, not just the shell PID.
  • Broken-mic detection — proactive zero-signal probe at launch and on app activation; red badge appears before you'd ever hit "Record" with a dead mic.
  • Asymmetric-silence indicator — menu-bar icon tints red on the side (mic top half / app bottom half) that stopped delivering audio mid-recording.
  • Speaker-naming dialog hardening — 750 ms keyboard-grace period prevents a stray Enter/Esc right after the meeting from auto-confirming "Speaker 1 / Speaker 2" placeholders. Hit ~19 % of historical sessions.
  • Off-main pipeline I/O — recoverOrphans, processed-recordings migration, and snapshot writes moved off the main actor; no more UI stalls on long log directories.
  • macOS notifications + verbose diagnostics — per-PID tap targets, output-device changes, and 5-second RMS streams for both channels are now logged under com.meetingtranscriber.audiotap.

Plus a refreshed README with an animated end-to-end pipeline hero, a Mermaid "How it works" diagram, and a Homebrew one-line install above the fold.

MeetingTranscriber 0.5.1

Installation

Via Homebrew (recommended):

brew tap pasrom/meeting-transcriber
brew install --cask meeting-transcriber

Manual:

  1. Download the DMG below
  2. Mount it and drag MeetingTranscriber to Applications

SHA256

2b20a85ad51a7ac0bf5b5087e624516d5fc5137f3bd646c2552af30954817935

What's Changed

🚀 Features

  • feat: add App Store build variant with APPSTORE flag by @pasrom in #29
  • feat: use PowerAssertionDetector for meeting detection by @pasrom in #30
  • feat(app): add custom protocol output directory by @pasrom in #34
  • feat(app): add elapsed time display to pipeline stages by @pasrom in #36
  • feat: add NVIDIA Parakeet TDT v3 as alternative ASR engine by @pasrom in #51
  • feat: add Qwen3-ASR as third transcription engine (macOS 15+) by @pasrom in #52
  • feat(app): pipeline warnings — surface non-fatal issues in menu bar by @pasrom in #60
  • feat(app): merge consecutive same-speaker segments + pipeline warnings integration by @pasrom in #61
  • feat(app): configurable protocol output language by @pasrom in #62
  • feat(app): None LLM provider + graceful protocol generation fallback by @pasrom in #63
  • feat(app): add VAD preprocessing via FluidAudio Silero v6 by @pasrom in #76
  • feat(app): add custom vocabulary support for Parakeet CTC boosting by @pasrom in #77
  • feat(app): add Sortformer diarizer mode for overlap-aware speaker diarization by @pasrom in #78
  • feat(app): permission health check — detect broken TCC entries by @pasrom in #90
  • feat(build): add Homebrew beta cask for pre-releases by @pasrom in #94
  • feat(app): add audioDebugLogging setting for forensic capture diagnostics by @pasrom in #117
  • feat(audiotap): expand audioDebugLogging with mic + device-detail + tap-format by @pasrom in #118
  • feat(app): surface known speaker names as quick-pick chips in naming dialog by @pasrom in #123
  • feat(app): track speaker recency for picker ranking by @pasrom in #124
  • feat(app): centroid-based speaker matching + embedding quality filter by @pasrom in #125
  • feat(app): log speaker recognition outcomes to JSONL by @pasrom in #126
  • feat(app): record top-3 match candidates per recognition event by @pasrom in #129
  • feat(app): show recognition stats in Settings by @pasrom in #130
  • feat(app): manage known voices via Settings by @pasrom in #131
  • feat(app): live-filter chip rows in speaker naming dialog by @pasrom in #132
  • feat(app): enroll speakers from existing recordings by @pasrom in #133
  • feat: allow re-opening speaker naming dialog after dismissal by @pasrom in #108
  • feat(app): debug RPC server skeleton with /state endpoint by @pasrom in #136
  • feat: record-only mode by @pasrom in #144
  • feat(app): persistent toggle for debug RPC server in Settings → Advanced by @pasrom in #147
  • feat: diagnostic logging end-to-end (toggle + export + redaction) by @pasrom in #152
  • feat(app): persistent file-based diagnostic logs (~30 day retention) by @pasrom in #154
  • feat(app): RPC routes for speaker DB testing by @pasrom in #161
  • feat(app): expose engine state in /state RPC for live observability by @pasrom in #207
  • feat(test): add WER/DER quality regression suite scaffolding by @pasrom in #209
  • feat(app): expose lastJob in RPC state snapshot by @pasrom in #217
  • feat(e2e): add live-recording E2E (deployed app + RPC driver) by @pasrom in #216
  • feat(rpc): /action/enqueueFile + chained reimport E2E by @pasrom in #248
  • feat(app): expose Parakeet language picker by @pasrom in #272
  • feat(app): import paired _app+_mic recordings as one dual-track job by @pasrom in #274
  • feat(app): per-channel signal indicator by @pasrom in #286
  • feat(app): detect symmetric-silence recordings (PR #286 follow-up) by @pasrom in #295
  • feat(app): tap whole bundle PID tree for Electron meeting apps by @pasrom in #307
  • feat(app): keyboard-grace period prevents accidental speaker-name confirms by @pasrom in #310

🐛 Bug Fixes

  • fix(build): apply entitlements for App Store builds without notarization by @pasrom in #33
  • fix(app): update Accessibility permission description by @pasrom in #35
  • fix(app): fix menu bar icon staying inactive during file processing by @pasrom in #43
  • fix(app): exclude manual recording from isWatching state by @pasrom in #44
  • fix(app): show error message in job row by @pasrom in #45
  • fix(app): mark recording as processed when job fails by @pasrom in #46
  • fix(app): improve mic device change handling with testable restart policy by @pasrom in #58
  • fix(app): hardening — crash prevention, thread safety, filename sanitization by @pasrom in #59
  • fix: detect actual channel count to prevent Mickey Mouse with mono USB devices by @pasrom in #67
  • fix(app): resolve SwiftFormat 0.60.1 lint violations by @pasrom in #75
  • fix: harden USB audio sample rate detection by @pasrom in #65
  • fix: correct sample rate detection for Bluetooth devices by @pasrom in #85
  • fix(ci): scope RC changelog to previous RC instead of last stable release by @pasrom in #93
  • fix: security hardening (path traversal, file permissions, shell quoting) by @pasrom in #97
  • fix(app): add KMeans crash recovery for FluidDiarizer by @pasrom in #98
  • fix(build): staple notarization ticket to .app and DMG by @pasrom in #91
  • fix(app): prevent mix.wav 2x duration from device restart mid-recording by @pasrom in #101
  • fix(app): use resampled audio for mix fallback when mic is unavailable by @pasrom in #105
  • fix(app): guard against missing mic hardware by @pasrom in #107
  • fix(audiotap+app): three teardown-safety bugs found in architecture review by @pasrom in #119
  • fix(app): keep naming sidecar when window closes without confirm by @pasrom in #134
  • fix(test): inject UserDefaults into AppSettings (parallel-safe) by @pasrom in #143
  • fix(app): warn about missing speaker recognition in Sortformer mode by @pasrom in #146
  • fix(app): debug RPC edge cases (env-var override + test pollution) by @pasrom in #149
  • fix(app): keep Output Folder picker interactive in record-only mode by @pasrom in #151
  • fix(app): SpeakerNamingView Confirm/Skip/Re-run unresponsive after multi-job switch by @pasrom in #156
  • fix(app): cache knownSpeakerNames on PipelineQueue (closes #155) by @pasrom in #158
  • fix(app): refresh PipelineQueue cache on speakers DB mutations by @pasrom in #159
  • fix(app): reset Re-run guard when dialog re-presents after diarization by @pasrom in #162
  • fix(app): also reset stepper + names when naming dialog re-presents by @pasrom in #163
  • fix(app): match actual transcript format when re-applying speaker names by @pasrom in #164
  • fix(app): lazy SpeakerMatcher init in Known Voices sheet by @pasrom in #167
  • fix(app): hygiene batch — dead branch, lazy disk reads, migration cleanup, ID helper by @pasrom in #177
  • fix(test): preserve fixture in ResamplingIntegrationTests pipeline tests by @pasrom in #176
  • fix(app): preserve fractional precision in speaker-snippet sample range by @pasrom in #178
  • fix(app): rotate persistent diagnostic log across day boundaries by @pasrom in #179
  • fix(app): re-arm Re-run button after each late diarization cycle by @pasrom in #181
  • fix(app): make speakers.json mutations race-free and gate RPC seeds by @pasrom in #182
  • fix: RPC quick-wins — constant-time auth, Host allowlist, mt-cli timeouts by @pasrom in #184
  • fix: small RPC + Settings hardening (PII-safe screenshots, off-main diagnostics, unique meeting slugs) by @pasrom in #185
  • fix(app): split statRow to bring type-checking under 300ms threshold by @pasrom in #198
  • fix(app): split statRow further to bring type-checking under 300ms by @pasrom in #200
  • fix(app): share RecognitionStatsLog actor between pipeline and SettingsView by @pasrom in #201
  • fix(app): open security scope on bookmark URL, not record-only child by @pasrom in #203
  • fix(app): rotate RPC bearer token on settings toggle off → on by @pasrom in #202
  • fix(app): auto-restart persistent log streamer on unexpected exit by @pasrom in #204
  • fix(app): propagate runtime AppSettings changes to engine instances by @pasrom in #206
  • fix(app): give up PersistentDiagnosticLog on instant-fail spawn (200 % CPU on macOS 26) by @pasrom in #218
  • fix(app): fall back to app-only diarization when mic track has no speakers by @pasrom in #219
  • fix(app): detach diagnostic-log readabilityHandler on subprocess EOF by @pasrom in #220
  • fix(e2e): respect E2E_ENABLED flag so e2e.yml actually runs engine tests by @pasrom in #221
  • fix(ci): restore keychain search list + dedup e2e runs by SHA by @pasrom in #238
  • fix(ci): release.yml stops swallowing Homebrew tap update failures by @pasrom in #242
  • fix(ci): install-developer-id prepends to keychain search list (don't drop mt-ci.keychain-db) by @pasrom in #241
  • fix(app): add 22 languages to WhisperKit picker by @pasrom in #263
  • fix(test): scope keychain delete to the one test that needs it by @pasrom in #282
  • fix(test): drop racy isRunning precondition in PersistentDiagnosticLog test by @pasrom in #289
  • fix(ci): align meeting-simulator binary path with e2e-app.sh (release) by @pasrom in #299
  • fix(tools): point meeting-simulator's findFixture() at the real fixture path by @pasrom in #298
  • fix(scripts): surface real failures in e2e-silent-recording instead of masking them by @pasrom in #301
  • fix(scripts): detect dev .app crash in e2e-app.sh polling loop by @pasrom in #302
  • fix(test): split testResamplePreservesSignalEnergy expression for stable type-check by @pasrom in #306
  • fix(app): detect zero-signal mic callbacks as broken microphone by @pasrom in #308

🧹 Maintenance

  • build(app): remove unnecessary entitlements by @pasrom in #26
  • refactor(app): use sandbox-compatible paths in AppPaths by @pasrom in #28
  • refactor(app): use macOS Keychain for secret storage by @pasrom in #27
  • ci: add App Store build to release workflow by @pasrom in #31
  • ci: use distinct DMG names for App Store build by @pasrom in #32
  • perf(app): cache MenuBarIcon animation frames at startup by @pasrom in #38
  • build(deps): bump actions/github-script from 7 to 8 by @dependabot[bot] in #39
  • build(deps): bump github.com/fluidinference/fluidaudio from 0.12.3 to 0.12.4 in /app/MeetingTranscriber by @dependabot[bot] in #40
  • build(deps): bump github.com/argmaxinc/whisperkit from 0.16.0 to 0.17.0 in /app/MeetingTranscriber by @dependabot[bot] in #41
  • refactor(app): extract AppState ViewModel + BadgeKind.compute pure function by @pasrom in #49
  • refactor(app): normalize all recorded audio to 16kHz at capture time by @pasrom in #50
  • test(app): comprehensive test coverage improvements by @pasrom in #56
  • test(app): add workflow integration tests for pipeline by @pasrom in #55
  • test(app): add FFT-based frequency preservation tests for AudioMixer by @pasrom in #57
  • chore(app): update dependencies to latest compatible versions by @pasrom in #64
  • chore(app): update FluidAudio to 0.13.4 by @pasrom in #74
  • build(deps): bump github.com/pointfreeco/swift-snapshot-testing from 1.19.1 to 1.19.2 in /app/MeetingTranscriber by @dependabot[bot] in #81
  • build(deps): bump github.com/argmaxinc/whisperkit from 0.17.0 to 0.18.0 in /app/MeetingTranscriber by @dependabot[bot] in #80
  • test: close critical test coverage gaps (+72 tests) by @pasrom in #86
  • test(app): add missing SwiftUI view tests by @pasrom in #87
  • test(app): add minor view test gap coverage by @pasrom in #88
  • test(app): add E2E integration tests by @pasrom in #89
  • build(deps): bump github.com/fluidinference/fluidaudio from 0.13.4 to 0.13.6 in /app/MeetingTranscriber by @dependabot[bot] in #92
  • ci: harden Dependabot pipeline against signing-secret absence by @pasrom in #111
  • build(deps): bump actions/github-script from 8 to 9 by @dependabot[bot] in #110
  • build(deps): bump dependabot/fetch-metadata from 2 to 3 by @dependabot[bot] in #114
  • refactor(audiotap): extract OutputDeviceChangeCoordinator as pure state machine by @pasrom in #120
  • build(deps): bump github.com/fluidinference/fluidaudio from 0.13.6 to 0.14.3 in /app/MeetingTranscriber by @dependabot[bot] in #121
  • chore(test): anonymize fixture names + drop unused q2 var by @pasrom in #135
  • chore(docs): drop stale swift-architecture.md by @pasrom in #137
  • test(snapshot): regenerate MenuBarIcon reference PNGs by @pasrom in #139
  • refactor(app): split SettingsView into 6 topic-grouped tabs by @pasrom in #141
  • test: harden two test-isolation bugs surfaced by --parallel by @pasrom in #140
  • ci: parallel Swift tests with cached ML models by @pasrom in #145
  • ci: skip macOS jobs and DMG build on docs-only PRs by @pasrom in #148
  • ci(release): mention beta channel in RC release notes by @pasrom in #150
  • test(app): regression tests for Skip and Re-run multi-job switching by @pasrom in #157
  • perf(app): cache subview sizes in ChipFlowLayout by @pasrom in #168
  • ci: cancel run when any job fails by @pasrom in #172
  • chore: gitignore docs/plans/.local for personal scratch by @pasrom in #173
  • ci: set predicate-quantifier=every for paths-filter by @pasrom in #175
  • perf(app): scope menu-bar timer to sub-view + skip ticks for static badges by @pasrom in #169
  • build(deps): bump github.com/fluidinference/fluidaudio from 0.14.3 to 0.14.4 in /app/MeetingTranscriber by @dependabot[bot] in #170
  • build(deps): bump github.com/argmaxinc/whisperkit from 0.18.0 to 1.0.0 in /app/MeetingTranscriber by @dependabot[bot] in #171
  • build: zero compiler warnings + warnings-as-errors gate by @pasrom in #187
  • ci(lint): close lint-coverage gap on mt-cli + audiotap tests by @pasrom in #188
  • test: integration coverage for RPC Host allowlist, slug uniqueness, mt-cli timeout by @pasrom in #186
  • build: warn on slow type-check sites (>300ms) by @pasrom in #190
  • build: full Swift 6 language mode (sources strict, tests v5-pinned) by @pasrom in #191
  • build(ci): pre-push release-parity check by @pasrom in #194
  • build(app): compile-perf threshold 500ms → 300ms by @pasrom in #193
  • test(app): factor out tmpDir setUp/tearDown into helper by @pasrom in #195
  • test(app): add makeTempFile + fixtureURL helpers, sweep ~30 sites by @pasrom in #196
  • ci: weekly build-performance tracking workflow by @pasrom in #197
  • ci: enable Thread- + AddressSanitizer in CI + fix race TSan caught by @pasrom in #208
  • ci(workflows): add quality-baseline job (push/cron/dispatch only) by @pasrom in #211
  • ci(workflows): extract Swift-test setup into composite action by @pasrom in #212
  • ci(workflows): split background QA into separate workflow by @pasrom in #213
  • ci(workflows): hygiene sweep on test matrices and pre-warm by @pasrom in #214
  • ci(e2e): pin DEVELOPER_DIR for self-hosted Mac runners by @pasrom in #215
  • test(quality): add diarization DER regression suite by @pasrom in #223
  • test(e2e): expand multi-format ingestion to all 7 fixture formats by @pasrom in #224
  • test(e2e): cover record-only path from handleMeeting with real fixture WAV by @pasrom in #225
  • test(e2e): VAD trim + engine + remap chain coverage by @pasrom in #226
  • ci(appstore): smoke-launch the App Store variant on push + nightly by @pasrom in #227
  • ci(e2e): --two-meetings flag for cooldown + state-reset coverage by @pasrom in #228
  • test(quality): add Parakeet WER coverage + hoist shared fixture helper by @pasrom in #229
  • ci: extract 4 composite actions + centralize paths-filter by @pasrom in #231
  • chore(ci): align actions/upload-artifact on v7 by @pasrom in #232
  • ci(e2e-app): pin live-recording job to audio-labeled runner by @pasrom in #233
  • ci(qa): move TSan + ASan matrix to self-hosted Mac mini by @pasrom in #234
  • ci(e2e): trigger on push to main so tag gate has data by @pasrom in #236
  • ci: declare stable-tag protection ruleset by @pasrom in #237
  • ci(e2e-app): drop paths-filter so push-event check-runs land on every main SHA by @pasrom in #240
  • chore(ci): drop redundant tags trigger from e2e.yml by @pasrom in #243
  • ci(install-developer-id): fail fast on keychain with no valid signing identity by @pasrom in #244
  • ci(setup-swift-test): pre-warm fails fast on rename or swift-test crash by @pasrom in #245
  • ci(security): wipe imported .p12 + SHA-pin ncipollo/release-action by @pasrom in #246
  • test(e2e-app): cover record-only mode with sidecar+WAV assertions by @pasrom in #247
  • ci: tighten job timeouts + step-level limits for timed_out visibility by @pasrom in #249
  • ci(e2e-app): fail fast with actionable error on Fast User Switching by @pasrom in #252
  • ci: add code coverage reporting via Codecov by @pasrom in #251
  • ci(codecov): tighten ignores + upload audiotap as separate flag by @pasrom in #255
  • chore(deps): bump github.com/fluidinference/fluidaudio from 0.14.4 to 0.14.5 in /app/MeetingTranscriber by @dependabot[bot] in #257
  • ci(quality): disable mt-ci keychain auto-lock during sanitizer runs by @pasrom in #259
  • test(app): cover ClaudeCLIProtocolGenerator parsing + path resolution by @pasrom in #261
  • ci(quality): bump SPM cache-key to invalidate corrupted cache by @pasrom in #262
  • test(app): cover VoiceEnrollmentView (0%→92%) + extract pure logic by @pasrom in #260
  • test(app): expand SpeakerNamingView coverage from 56% to 75% by @pasrom in #265
  • test(app): cover makeSpeakerDBActions closures via temp-path SpeakerMatcher by @pasrom in #264
  • test(app): expand KnownVoicesView coverage from 53% to 57% by @pasrom in #266
  • ci(mini): atomic keychain-prepend helper + migrate all call sites by @pasrom in #268
  • test(app): extract longestSegment helper + cover infinite-container layout by @pasrom in #267
  • test(app): extract 3 pure helpers from KnownVoicesView + cover them by @pasrom in #269
  • test(app): extract OutputSettings display helpers + fix path-prefix bug by @pasrom in #270
  • ci(e2e-app): chain record-only + reimport via --reimport-latest by @pasrom in #273
  • refactor(app): extract WatchLoopEndPolicy as pure decision function by @pasrom in #275
  • refactor(app): inject Clock into WatchLoop for deterministic async tests by @pasrom in #276
  • refactor(app): introduce WatchLoopState snapshot value type by @pasrom in #277
  • refactor(app): funnel WatchLoop mutations through update(_:) by @pasrom in #278
  • refactor(app): extract ManualRecordingMonitorPolicy as pure decision function by @pasrom in #279
  • ci(mini): exclude ~/Library/Keychains from Spotlight via .metadata_never_index by @pasrom in #280
  • refactor(app): extract PipelineSnapshot as pure I/O helper by @pasrom in #281
  • test(app): isolate AppStateTests pipeline-queue snapshot per test method by @pasrom in #283
  • perf(app): dispatch PipelineQueue.saveSnapshot off the main actor by @pasrom in #284
  • perf(app): move recoverOrphanedRecordings dir scan off the main actor by @pasrom in #285
  • ci(e2e-app): add codesign pre-flight smoke test to catch trustd drift early by @pasrom in #287
  • perf(app): move migrateProcessedRecordings off the main actor by @pasrom in #288
  • test(app): cover ClaudeCLI subprocess builders via three pure helpers by @pasrom in #290
  • refactor(app): extract ProtocolGenerator.buildSystemPrompt shared by both LLM generators by @pasrom in #291
  • test(app): cover AppState.makeProtocolGenerator .none provider branch by @pasrom in #292
  • refactor(scripts): extract shared e2e helpers into scripts/lib/ by @pasrom in #296
  • ci(e2e-app): run silent-recording detector e2e as additional lane by @pasrom in #297
  • perf(scripts): collapse 3× jq invocations per polling tick into one @TSV pass by @pasrom in #304
  • refactor(scripts): add snapshot_default + restore_float_default helpers by @pasrom in #305
  • test(audiotap): cover Helpers.swift end-to-end (9% → ~98%) by @pasrom in #309
  • test(app): extract + cover three ClaudeCLI pure helpers by @pasrom in #311
  • chore(deps): bump github.com/fluidinference/fluidaudio from 0.14.5 to 0.14.7 in /app/MeetingTranscriber by @dependabot[bot] in #312
  • test(app): extract + cover three FFmpegHelper pure helpers by @pasrom in #313
  • test(app): extract Parakeet token-grouping + Qwen3 chunking into testable sibling types by @pasrom in #314
  • test: cover DebugRMSReporter.tick() + PIDTranslation + NotificationManager.notificationContent by @pasrom in #315

📖 Documentation

  • docs(docs): add branch protection rules to CLAUDE.md by @pasrom in #25
  • docs: update WhisperKit dual-source API references by @pasrom in #37
  • docs: complete architecture docs for Parakeet + Qwen3 engines by @pasrom in #53
  • docs: update README for Parakeet and Qwen3 transcription engines by @pasrom in #54
  • docs: update documentation to match current codebase by @pasrom in #68
  • docs: update CLAUDE.md to match current codebase by @pasrom in #71
  • docs: remove non-existent generate_test_audio_10speakers.sh from CLAUDE.md by @pasrom in #72
  • docs: update documentation to match current codebase by @pasrom in #73
  • docs: permission problem badge + ignore .worktrees by @pasrom in #95
  • docs: document Homebrew beta cask by @pasrom in #96
  • docs: add PermissionHealthCheck.swift to project structure and architecture notes by @pasrom in #102
  • docs: update documentation to match current codebase by @pasrom in #103
  • docs(rpc): document debug RPC + restore pipeline diagram by @pasrom in #138
  • docs: update documentation to match current codebase by @pasrom in #142
  • docs(arch): no expensive work in SwiftUI hot paths by @pasrom in #160
  • docs(claude): document docs/plans/.local convention by @pasrom in #174
  • docs(claude): forbid referencing .local/ content in shared artifacts by @pasrom in #210
  • docs(readme): add Testing & CI section highlighting on-device E2E by @pasrom in #222
  • docs(readme): add status badges for E2E + quality + App Store workflows by @pasrom in #250
  • docs: update documentation to match current codebase by @pasrom in #153
  • docs(arch): document menu-bar state animations + overlay precedence by @pasrom in #293
  • docs(readme): document per-channel asymmetric-silence indicator with GIFs by @pasrom in #294
  • docs(readme): refresh hero with end-to-end pipeline GIF by @pasrom in #316
  • docs(readme): render "How it works" pipeline as Mermaid by @pasrom in #317

Other Changes

  • concurrency(test): migrate test target to Swift 6 by @pasrom in #192
  • concurrency(app): preemptively guard remaining AV/AX imports by @pasrom in #199
  • Revert: ci(e2e) concurrency group back to github.ref by @pasrom in #239

Full Changelog: v0.4.1...v0.5.1