Skip to content

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 01 Aug 07:51
· 258 commits to main since this release

⚠️ One-time setup after this update

This release changes the app's bundle identifier from com.meetingtranscriber.app to app.meetingtranscriber. macOS ties privacy permissions to that identifier, so it treats this build as a new app and asks for them again. Grant them once, or recording stays silent.

  1. Microphone: macOS asks on the next recording. Click Allow.
  2. Audio from other apps: macOS asks separately, right before the first recording of a meeting app's own audio. This prompt is new in 0.6.0 for everyone. Allow it, or the other participants' side of the meeting stays silent.
  3. Screen Recording: optional. Meeting detection does not need it, the app detects calls through IOKit power assertions. The grant only sharpens the meeting title and acts as a fallback for step 2. If you want it, switch MeetingTranscriber on under Privacy & Security → Screen & System Audio Recording and relaunch the app.

Nothing else is lost. Your settings carry over automatically (engine, language, watched apps, output folder, diarization tuning), and voice profiles, recordings, custom prompts and downloaded models are untouched. If you stored an OpenAI API key, macOS may ask once for keychain access; allowing it is permanent.

The old entry may still be listed under Screen & System Audio Recording. It does nothing and can be removed with the minus button.

Why the rename: the old identifier could not be registered with Apple, which blocked the entitlement that lets the browser-meeting consent prompt break through Focus and Do Not Disturb. Without it, that prompt was invisible to anyone with a Focus mode on, and browser meetings were never recorded.


MeetingTranscriber 0.6.0

Installation

Via Homebrew (recommended):

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

Homebrew 6.0+ may flag the third-party tap as untrusted. If so, run brew trust --tap pasrom/meeting-transcriber before installing.

Manual:

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

SHA256

c1bd9437c9d2f8c168e61df42d9e5a05591298c7e2515795fd94179b9b9ba475

Notable fix: Zoom auto-detection

Zoom meetings are auto-detected again. Zoom stopped putting "zoom" in the name of the power assertion it holds during a call (it now ships Apple's sample placeholder "Describe Activity Type"), so auto-detection in 0.5.1 silently never fired for Zoom while manual recording kept working. Detection now matches on the assertion type as well, so live Zoom calls start recording on their own again. Teams and Webex were unaffected.

If you are on 0.5.1 and Zoom never started recording by itself, this update is all you need. It presented as a permissions problem (Screen Recording, TCC, Homebrew quarantine), but it was not one.

What's Changed

🚀 Features

  • feat: live transcription overlay (PoC) by @pasrom in #318
  • feat(app): expose experimental diarization tuning in Settings (#165 phase 5) by @pasrom in #180
  • feat(app): extract WeSpeaker embeddings post-hoc in Sortformer mode by @pasrom in #325
  • feat(app): link diarizer mode + speaker count across Settings & naming dialog by @pasrom in #326
  • feat(app): match enrolled speakers per-final in live caption overlay by @pasrom in #328
  • feat(docs): add project landing page deployed via GitHub Pages by @pasrom in #378
  • feat(app): recover crashed recordings by re-mixing the raw temp (issue #379 durability, part 3) by @pasrom in #385
  • feat(app): resource-usage self-report (GET /metrics) + e2e CPU-load measurement lane by @pasrom in #390
  • feat(app): English low-latency live captions via streaming EOU ASR (opt-in) by @pasrom in #399
  • feat(app): per-stage processing duration metrics (avg + live-vs-typical) by @pasrom in #430
  • feat(app): stage-timing norms — live 'longer than usual' hint + per-config Settings by @pasrom in #432
  • feat(app): per-job status readback for local automation API (#431) by @pasrom in #436
  • feat(app): /v1/jobs//naming endpoints for headless speaker naming (#431) by @pasrom in #437
  • feat(app): blocking POST /v1/transcribe endpoint (#431) by @pasrom in #438
  • feat(app): /v1 automation API polish — idempotency, 409, relabel (#431) by @pasrom in #439
  • feat(app): multilingual Nemotron live captions via language-driven backend by @pasrom in #445
  • feat(app): expose posted notifications in the debug RPC state by @pasrom in #460
  • feat(app): expose effective settings in the debug RPC state by @pasrom in #461
  • feat(app): expose the menu-bar badge in the RPC /state snapshot by @pasrom in #469
  • feat(app): expose update-checker status in the RPC /state snapshot by @pasrom in #471
  • feat(app): expose isManualRecording in the RPC /state snapshot by @pasrom in #473
  • feat(app): inline transcript in /v1 response via ?include=transcript by @pasrom in #478
  • feat(app): filter meeting detection by the Apps to Watch toggles by @pasrom in #489
  • feat(app): add clickable help badges to settings options by @pasrom in #507
  • feat(app): read-only GET /ui/tree accessibility-tree RPC endpoint by @pasrom in #515
  • feat(app): expose home-dir-redacted title labels in GET /ui/tree by @pasrom in #516
  • feat(app): add /ui/press + drive /ui/* via self-pid AXUIElement (in-process UI automation) by @pasrom in #517
  • feat(app): detect and record browser meetings (issue #503) by @pasrom in #523
  • feat(app): automate text entry via POST /ui/type by @pasrom in #533
  • feat(app): accept AMR, 3GP and Opus voice recordings on import by @pasrom in #545
  • feat(app): record-only sidecar tells consumers whether a capture was auto or manual by @pasrom in #555
  • feat(app): MicInputDetector — detect WeChat / Tencent Meeting / FaceTime / WhatsApp calls via Core Audio mic-input state by @jianjettfu-oss in #548
  • feat(app): write the transcript before diarization runs by @pasrom in #567

🐛 Bug Fixes

  • fix(scripts): poll for menu-bar red tint in channel-health e2e by @pasrom in #322
  • fix(app): pick temporally-pure sample for speaker-naming dialog (#165 phase 4) by @pasrom in #166
  • fix(app): dedupe FluidVAD lazy init via single-flight Task by @pasrom in #323
  • fix(app): finalize in-flight auto-recording on Stop Watching by @pasrom in #331
  • fix(app): Known Voices sheet opens empty on first tap by @pasrom in #332
  • fix(app): enable WhisperKit language detection for auto-detect by @pasrom in #340
  • fix(app): restrict sensitive file writes to owner-only (0600) by @pasrom in #343
  • fix(audiotap): make IOProc-shared fields atomic to close data races by @pasrom in #341
  • fix(app): prevent app abort on Claude CLI stdin broken pipe by @pasrom in #342
  • fix(app): make .private explicit for meeting-title logging by @pasrom in #347
  • fix(app): keep app-track speaker names in the mic-fail diarization fallback by @pasrom in #353
  • fix(app): apply mic-fail fallback to late re-diarization by @pasrom in #354
  • fix(ci): avoid SIGPIPE in e2e-app --reimport-latest mix selection by @pasrom in #356
  • fix(app): reserve "Remote" mic label to prevent dual-track double-count by @pasrom in #357
  • fix(ci): stop e2e-app failing on a recovered stale pipeline job by @pasrom in #368
  • fix(app): accept OpenAI base URL, not only full chat-completions URL by @pasrom in #366
  • fix(app): shift dual-track mic diarization by micDelay to match transcript by @pasrom in #370
  • fix(app): keep rpcStateSnapshot under the 300ms type-check budget by @pasrom in #372
  • fix(app): recover from mic device-change crash (issue #379) by @pasrom in #381
  • fix(app): repair unfinalized recordings on launch (issue #379 durability) by @pasrom in #384
  • fix(app): keep recordings rate- and wall-clock-correct across mid-session device changes by @pasrom in #392
  • fix(app): recover pre-upgrade raw temps with their legacy format by @pasrom in #393
  • fix(app): flush pending live utterance when recording stops by @pasrom in #397
  • fix(app): debug RPC server listener lifecycle + orphaned log-streamer reaper by @pasrom in #401
  • fix(app): avoid splitting Parakeet tokens mid-word at the token cap by @devdotbo in #403
  • fix(app): reject negative Content-Length in RPC request parser by @pasrom in #405
  • fix(audiotap): give the mic resampler output-buffer headroom by @pasrom in #406
  • fix(app): hoist MenuBarView body sections under the type-check budget by @pasrom in #408
  • fix(app): keep the app temp until the mix is durable so recovery can retry by @pasrom in #410
  • fix(app): skip recently-modified WAVs in the launch repair scan by @pasrom in #411
  • fix(app): guard toggleWatching against a re-entrant start by @pasrom in #413
  • fix(app): make the requested speaker count actually force that many speakers by @pasrom in #415
  • fix(ci): floor build-perf slowdown alerts on absolute seconds by @pasrom in #429
  • fix(app): bound OpenAI protocol generation with a hard total deadline by @pasrom in #433
  • fix(app): cap protocol output tokens + surface truncation honestly by @pasrom in #434
  • fix(app): diagnosable + non-false-positive permission health check (#446) by @pasrom in #447
  • fix(app): log failure causes publicly instead of by @pasrom in #450
  • fix(app): rebuild transcript from late re-diarization on confirm by @pasrom in #453
  • fix(app): stop menu-bar animation timer crashing during status-bar menu tracking by @chrisworksai in #452
  • fix(app): harden the RPC settings snapshot (read-only bookmark resolution) by @pasrom in #462
  • fix(app): skip no-op transitions in PipelineQueue.updateJobState by @pasrom in #465
  • fix(app): apply WhisperKit model changes without a relaunch by @pasrom in #474
  • fix(app): detect Zoom's placeholder power assertion on macOS 26 by @pasrom in #475
  • fix(app): fall back to mic-only diarization when the app track fails by @pasrom in #476
  • fix(app): guard MicCaptureHandler stop() tap teardown on input-less hosts by @pasrom in #487
  • fix(app): skip queue rebuild while the queue owns unfinished work by @pasrom in #490
  • fix(app): defer live-caption re-warm when settings change mid-recording by @pasrom in #500
  • fix(app): name meeting outputs from the meeting-start time, in one basename by @pasrom in #502
  • fix(app): keep speaker-naming window visible when it loses focus by @pasrom in #508
  • fix(app): use the real meeting window title, not the Teams Calendar tab by @pasrom in #506
  • fix(test): assert only env-stable pin flags in e2e-app naming lane (#504) by @pasrom in #511
  • fix(app): anchor meeting-start on wall-clock Date captured at recording start by @pasrom in #512
  • fix(ci): guard DebugRPCServer+*.swift in e2e-ui-smoke (GitHub + is a quantifier) by @pasrom in #521
  • fix(ci): harden the UI smoke lane against local runs and blind spots by @pasrom in #534
  • fix(app): guard what /ui/type types into and which window /ui/* posts to by @pasrom in #535
  • fix(ci): let a dispatched soak choose its sampling interval by @pasrom in #537
  • fix(ci): keep the soak's meeting alive for the whole sampling window by @pasrom in #539
  • fix(app): surface denied notifications that silently disable browser meetings by @pasrom in #541
  • fix(app): add missing NSAudioCaptureUsageDescription for the process tap by @jianjettfu-oss in #547
  • fix(app)!: make the browser-consent prompt survive Focus modes (new bundle identifier, #543) by @pasrom in #546
  • fix(app): make the release actually deliver the entitlement, and let users grant Screen Recording by @pasrom in #552
  • fix(test): gate the live transcript on German, not on the fixture's script by @pasrom in #554
  • fix(app): stop relocating user-picked import files by @pasrom in #553
  • fix(app): mic track is silent whenever another app activates voice processing (discrete channel layout downmix) by @jianjettfu-oss in #550
  • fix(app): make the browser-consent prompt observable, and stop it blocking detection by @pasrom in #556
  • fix(app): stop treating an unseen consent prompt as a refusal by @pasrom in #557
  • fix(app): stop concurrent pipeline runs from destroying each other's work by @pasrom in #561
  • fix(app): keep the transcript when diarization loses its audio by @pasrom in #565
  • fix(app): stop a second queue from running a job already in flight by @pasrom in #564
  • fix(app): make cancelling a job stop the recording coming back by @pasrom in #568

🧹 Maintenance

  • chore(build): bump tool packages to swift-tools-version 6.0 by @pasrom in #319
  • chore(build): migrate AudioTapLib to Swift 6 strict concurrency (.v6) by @pasrom in #320
  • chore(build): remove unused whisperkit-cli tool by @pasrom in #321
  • perf(app): skip segmentation model load in LiveSpeakerMatcher cold-start by @pasrom in #329
  • refactor(app): extract testable buildRecording from DualSourceRecorder.stop() by @pasrom in #330
  • build(app): bump transitive swift-argument-parser 1.7.0 → 1.7.1 by @pasrom in #333
  • refactor(scripts): extract poll_until helper for e2e drivers by @pasrom in #334
  • refactor(app): extract processNext() into focused pipeline stage methods by @pasrom in #335
  • perf(app): cut AppState.init type-check time below the 300ms limit by @pasrom in #344
  • test(app): cover OpenAIProtocolGenerator transport-error paths by @pasrom in #346
  • ci: gate quality runs against a committed WER/DER baseline by @pasrom in #349
  • refactor(app): unify diarize() speaker-assignment paths into DiarizationProcess.labelSegments by @pasrom in #351
  • refactor(app): decompose diarize() into focused stages, drop lint suppression by @pasrom in #352
  • test(app): pin WatchLoop's recorder start-arg threading by @pasrom in #355
  • refactor(app): extract PermissionsController from AppState by @pasrom in #358
  • refactor(app): extract ChannelHealthController from AppState by @pasrom in #359
  • refactor(app): extract LiveTranscriptionCoordinator from AppState by @pasrom in #360
  • refactor(app): extract RPCServerController from AppState by @pasrom in #361
  • test(app): cover LiveTranscriptionCoordinator sink-attach gate by @pasrom in #362
  • refactor(app): extract PipelineController from AppState by @pasrom in #364
  • refactor(app): extract WatchingController from AppState by @pasrom in #365
  • refactor(app): extract EngineController from AppState (final god-class split) by @pasrom in #367
  • refactor(app): unify engine loadModel dedup via shared SingleFlight by @pasrom in #369
  • test(app): assert manual-start threads the pid to the recorder by @pasrom in #371
  • chore: name copyright holder in MIT LICENSE by @pasrom in #373
  • test(app): pin StoredSpeaker.id Identifiable contract by @pasrom in #374
  • test(app): pin late-rerun dual-track micDelay shift by @pasrom in #375
  • build(deps): bump github.com/fluidinference/fluidaudio from 0.14.7 to 0.14.8 in /app/MeetingTranscriber by @dependabot[bot] in #377
  • chore(docs): serve landing page from custom domain meetingtranscriber.app by @pasrom in #380
  • ci: skip app build and tests for landing-site-only changes by @pasrom in #383
  • test(test): fix crash-recovery e2e to assert the pipeline job, not the transient mix file (issue #379) by @pasrom in #386
  • ci(e2e-app): run crash-recovery e2e as always-run final lane (issue #379) by @pasrom in #387
  • ci(e2e-mic-device-change): run nightly + restore canonical bundle (issue #379) by @pasrom in #388
  • ci(e2e): collect cpu-load trend data nightly by @pasrom in #391
  • ci(e2e): clean up cpu-load lane recording artifacts on exit by @pasrom in #394
  • perf(app): forward resampled 16k audio to the app live sink by @pasrom in #396
  • perf(app): halve live-caption re-transcription cadence by @pasrom in #398
  • ci: harden against slow-runner flakes and self-masking cancellations by @pasrom in #402
  • ci: pipefail the tee'd report/analyze steps so failures aren't masked by @pasrom in #407
  • chore: gitignore Claude Code local settings and in-repo worktrees by @pasrom in #409
  • ci: sweep this-run recording orphans in the silent-recording e2e by @pasrom in #412
  • test(app): diarize backtest recordings dual-track instead of the mix by @pasrom in #421
  • build(deps): bump actions/upload-pages-artifact from 3 to 5 by @dependabot[bot] in #416
  • build(deps): bump actions/deploy-pages from 4 to 5 by @dependabot[bot] in #417
  • build(deps): bump actions/checkout from 4 to 6 by @dependabot[bot] in #419
  • build(deps): bump github.com/fluidinference/fluidaudio from 0.14.8 to 0.15.2 in /app/MeetingTranscriber by @dependabot[bot] in #420
  • build(deps): bump actions/configure-pages from 5 to 6 by @dependabot[bot] in #418
  • chore(build): drop swiftlint disables made superfluous by 0.63.3 by @pasrom in #425
  • refactor(app): feed live captions through bounded per-channel AsyncStreams by @pasrom in #424
  • refactor(app): extract SpeakerNamingStore from PipelineQueue by @pasrom in #426
  • refactor(app): own EngineModelState + skip diarization without per-utterance timestamps by @pasrom in #427
  • refactor(app): move mergeDualSourceSegments to DiarizationProcess by @pasrom in #428
  • perf(app): skip JSON re-parse in SpeakerMatcher migration fast-path by @pasrom in #435
  • chore(app): remove Qwen3 ASR engine + bump FluidAudio to 0.15.4 by @pasrom in #444
  • test(ci): RPC permission-health exposure + e2e for the #446 probes by @pasrom in #449
  • build(deps): bump actions/checkout from 6 to 7 by @dependabot[bot] in #455
  • build(deps): bump actions/cache from 5 to 6 by @dependabot[bot] in #454
  • ci: add label-gated PR triggers to the heavy test lanes by @pasrom in #459
  • test(ci): add speaker-naming-confirm E2E lane by @pasrom in #463
  • refactor(app): converge speaker-naming test handler onto the production flow by @pasrom in #457
  • refactor(app): extract SpeakerNamingSession from PipelineQueue by @pasrom in #458
  • refactor(app): extract ProcessedRecordingsLedger from PipelineQueue by @pasrom in #466
  • test(ci): assert transcript content in the live-recording E2E lane by @pasrom in #464
  • refactor(app): extract pipeline event log from PipelineQueue by @pasrom in #467
  • refactor(app): split PipelineQueue stage + recovery clusters into extensions by @pasrom in #468
  • refactor(app): introduce SpeakerKey value type for speaker-track identity by @pasrom in #470
  • refactor(app): route DiarizationProcess R_/M_ prefixes through SpeakerKey by @pasrom in #472
  • ci(release): make the GitHub Release step idempotent on re-runs by @pasrom in #477
  • test: cover high-value untested branches in pipeline, diarization, and audio by @pasrom in #482
  • test(app): cover mutation-survivor edge branches (WAV repair, StoredSpeaker, UpdateChecker, ChannelHealth) by @pasrom in #483
  • test(app): cover mutation-survivor edge branches (drift guard, centroid guards, overlap tie-break) by @pasrom in #484
  • test(app): cover HTTPRequest.parse residual edges (own test file) by @pasrom in #485
  • test(app): cover /v1 transcribe wait clamping and empty idempotent enqueue by @pasrom in #486
  • build(deps): bump github.com/pointfreeco/swift-snapshot-testing from 1.19.2 to 1.19.3 in /app/MeetingTranscriber by @dependabot[bot] in #479
  • build(deps): bump github.com/fluidinference/fluidaudio from 0.15.4 to 0.15.5 in /app/MeetingTranscriber by @dependabot[bot] in #480
  • refactor(app): make AudioCaptureSession.stop() result arithmetic unit-testable by @pasrom in #488
  • refactor(app): extract FluidVAD speech-region math into a testable seam by @pasrom in #491
  • ci(e2e): run crash-recovery nightly and on run-e2e label by @pasrom in #492
  • refactor(app): extract sample-rate priority ladder into SampleRateQuery.chooseRate by @pasrom in #493
  • refactor(app): dedup the sample-rate divergence warning by @pasrom in #494
  • refactor(app): add an injected seam for DualSourceRecorder.resolveTapPIDs by @pasrom in #495
  • test(app): cover the DebugRPCServer request-size cap + chunked framing by @pasrom in #496
  • perf(app): stop the meeting-start system slowdown from concurrent model warm-up by @pasrom in #499
  • test(app): CI regression net for speaker-naming window pinning (#504) by @pasrom in #509
  • build(ci): pin SwiftFormat to 0.61.1 by @pasrom in #513
  • build(ci): make pinned SwiftFormat win PATH over runner default by @pasrom in #514
  • ci: add GitHub-hosted /ui/* self-pid AX canary lane by @pasrom in #518
  • test(app): Picker/Stepper write-back + LiveCaptionsOverlay ViewInspector tests by @pasrom in #519
  • refactor(app): A11yID accessibility-identifier namespace + GUI testing practice by @pasrom in #520
  • test(app): browser-meeting detection e2e capture-proof lane (#503) by @pasrom in #525
  • refactor(app): NotificationManager behind a port for testable consent behavior (#503) by @pasrom in #526
  • test(ci): add a long-session soak lane by @pasrom in #536
  • test(app): add a real-meeting quality fixture and make DER overlap-aware by @pasrom in #540
  • build(deps): bump github.com/pointfreeco/swift-snapshot-testing from 1.19.3 to 1.19.4 in /app/MeetingTranscriber by @dependabot[bot] in #544

📖 Documentation

  • docs: sync CLAUDE.md and architecture-macos.md with current sources by @pasrom in #253
  • docs: update documentation to match current codebase by @pasrom in #324
  • docs(app): sync stale comments and notes with the code by @pasrom in #345
  • docs: add GitHub social-preview card by @pasrom in #350
  • docs: correct stale references in CLAUDE.md by @pasrom in #376
  • docs(site): add favicon to landing site by @pasrom in #382
  • docs: correct self-hosted-runner TCC mechanism (no PPPC profile) by @pasrom in #389
  • docs: link release badge to the releases list by @pasrom in #395
  • docs: sync CLAUDE.md and architecture-macos.md with the current codebase by @pasrom in #336
  • docs(ci): correct the fail-fast comment to match cancel-on-failure by @pasrom in #414
  • docs: document the Local Automation API (#431) by @pasrom in #442
  • docs: note Homebrew 6.0 tap trust across install instructions by @pasrom in #443
  • docs: reconcile GUI-testing doc with shipped test reality by @pasrom in #522
  • docs: move E2E Architecture and Distribution to lazy-loaded skills by @pasrom in #528
  • docs: slim CLAUDE.md Project Structure to a curated module overview by @pasrom in #529
  • docs: update documentation to match current codebase by @pasrom in #530
  • docs(app): say what the processed ledger actually records by @pasrom in #566
  • docs: clarify meeting detection needs no Screen Recording by @pasrom in #569

Other Changes

  • Post-merge review follow-ups: ingest doc, OpenAI branch tests, ffmpeg log privacy by @pasrom in #348
  • ci: label fork PRs via pull_request_target by @pasrom in #404
  • Diagnostics + guidance for a silent app-audio track (#524) by @pasrom in #559
  • Unify the Screen Recording pane path behind one versioned constant by @pasrom in #560

New Contributors

Full Changelog: v0.5.1...v0.6.0