Releases
Changelog
Every Daisy release with notes. Sourced from the same appcast.xml Sparkle uses to serve auto-updates — when a new version ships, this page reflects it.
Daisy 1.0.7.3
build 40 · 28 May 2026
What's new in 1.0.7.3
- **Native Ollama provider (build 40).** Settings → Summary → Provider now offers "Ollama (local)" as a first-class option, posting to your local `ollama serve` at `127.0.0.1:11434/api/chat` directly — no API key, no network egress, no MCP shim required. Pre-build-40 the "Ollama" preset was misleadingly hidden under the MCP provider with a JSON template that assumed an MCP+SSE wrapper was running; the pre-PH audit (2026-05-28) caught it as a P0 blocker because stock Ollama doesn't speak MCP. Now: pull a model (`ollama pull llama3.2:latest`), pick Ollama in Settings, done. Recommended models pre-listed (Llama 3.2, Qwen 2.5, Mistral, Gemma 2, GPT-OSS); custom tags accepted via free-text field. Wider `num_ctx: 16384` than Ollama's default 2048 so a 30-min meeting transcript actually fits the context window.
- **Native LM Studio provider (build 40).** Same rescue as Ollama. New "LM Studio (local)" provider hits its OpenAI-compatible `/v1/chat/completions` REST on `127.0.0.1:1234` directly. Load a model in the LM Studio app, click Developer → Start, paste the API identifier from LM Studio's UI into Daisy's Settings, you're set. No API key, no MCP. Same recommended-models list (Qwen, Llama, Mistral, Gemma) plus free-text override.
- **Three audio races flagged by pre-PH macOS audit (build 40).** (1) `SystemAudioCapture.buffers` assigned/cleared `bufferContinuation` without the `outputQueue.sync` fence the other `nonisolated(unsafe)` fields used — could race a sample-buffer callback that's mid-yield against the assignment from MainActor. Now both branches go through the queue. (2) `RecordingSession.pause()` fired an unawaited `Task { await systemAudio.pause() }` and then flipped `status = .paused` immediately — user who pause→resume'd quickly caught the detached pause Task still in flight, and `SystemAudio.resume()`'s `guard state == .paused` silently no-op'd → system audio went silent until full Stop+Start. Now `pause()` is async and awaits the systemAudio pause before flipping status. (3) Same pattern in `failFast()` (the early-exit cleanup path inside `start()`): unawaited `systemAudio.stop()` raced with the next start() if the user retried after a "Whisper not ready" early-exit. Now `failFast()` is async and awaits the stop.
- **MCPSummarizer reachability probe (build 40).** `isReady()` was a non-empty-string check that always returned `.available`, even when the MCP server was off. User would record a 45-min meeting, hit Stop, then wait through a long handshake-timeout error. Now: 2s TCP probe against the SSE endpoint with a wall-clock deadline race so an open-but-blocking SSE stream doesn't pin the Settings UI.
- **Removed misleading Ollama / LM Studio MCP presets (build 40).** The MCP "Quick setup" menu used to offer Ollama / LM Studio / llama.cpp wrapper templates. Ollama and LM Studio templates dropped — those products now have dedicated native adapters that work without an MCP shim. llama.cpp's preset stays because llama.cpp's own REST server (`./server`) is genuinely speaking its own protocol, and users running it through an MCP wrapper is more common.
- **Route-change recovery: full engine rebuild on macOS 26.5 format de-sync (build 39).** Build 34's `engine.reset()` fix turned out to be insufficient on macOS 26.5 — Apple's AVAudioEngine keeps the cached inputNode format across reset() when the AUHAL is swapped to a new device. Real-world repro: EarPods unplug mid-recording → ConfigurationChange fires → reset() runs → `outputFormat(forBus:)` still reports 44.1 kHz from the disconnected EarPods while CoreAudio shows the now-active BuiltIn mic at 48 kHz. The defensive cross-check guard caught the mismatch and stopped the crash (good!) but then the session fell to paused with no auto-recovery — user saw "audio doesn't record" until manual Stop+Record. Fixed by adding `rebuildEngineAndRetry()`: when the de-sync guard fires, completely throw away the AVAudioEngine instance, build a new one, re-pin the device, re-install the tap, re-register the ConfigurationChange observer, and restart. The programmatic equivalent of Stop+Record. If even the rebuilt engine can't get a coherent format → falls to paused with the existing "Hit Resume to retry" toast (defense in depth). Archive rolls into a new `.partN.caf` if the new device has a different sample rate, same as the in-place recovery path.
- **System audio archive truncation on resume (build 39).** SystemAudio writer was hitting "3,645,120 frames written, 0 write errors, 0 bytes on disk" in the same build-37 crash log. Root cause: `start()` unconditionally stamped a zero-byte placeholder file via `FileManager.createFile()` on every call — including resume from `.paused`, when `archiveWriter` was still holding an open ExtAudioFile pointing at that same path. `createFile()` detaches the inode the open descriptor is writing into: writes keep returning success to ARC but they stream into an orphaned block, while the directory entry stays at the placeholder zero. Same pattern as the 1.0.7.1 mic-writer fix; this one snuck through on the system-audio side. Fix: gate the placeholder behind `isFreshStart` (only stamp on first start, not on resume) so the live writer's file is never overwritten.
- **4th macOS 26.2 UAF surface — Picker (`.inline` / `.segmented`) (build 37).** Repro: open Settings → Diarization (with the Per speaker / Me vs. others picker), bounce a recording start → silent failure → restart. Crash stack: `swift_task_isCurrentExecutor → SystemSegmentedControl._overrideSizeThatFits → DesignLibrary HStack closure → EXC_BAD_ACCESS` at an unmapped address — same Apple-side UAF in the Swift concurrency ↔ AppKit metadata bridge we've now hit four ways on Tahoe (sidebar toggle, MCP storm layout pressure, Liquid Glass material, now segmented-control wrappers). SwiftUI's `.inline` and `.segmented` picker styles on macOS both render through `SystemSegmentedControl`, which is an `NSSegmentedControl` wrapper — same NSCell family. Mitigations: Diarization picker switched to `.radioGroup` (real `NSButton`s, not on any 26.x UAF stack), Notion parent-kind and MCP/Webhook kind pickers switched to `.menu` (`NSPopUpButton`, also safe family). Visual change: stacked radios for diarization, popup menu for the two settings — both read more "settings" anyway. Will restore segmented post-26.x once Apple ships a fix.
- **UX cleanup — "Split" / "Two sides" renamed to "Per speaker" / "Me vs. others" (build 37).** Caught in the same crash thread: the diarization picker labels collided with the sidebar's "Recording both sides" status pill (which describes mic + system audio being captured, totally different concept). New labels describe what shows up in the transcript — per-speaker rows vs. just me and everyone-else — without borrowing the "sides" vocabulary.
- **Sparkle end-to-end update flow validation (build 36).** No code changes from build 35 — pure validation release so build-35 users can hit Check for Updates and empirically confirm the post-`SUEnableInstallerLauncherService`-removal install path actually works (Sparkle docs + Discussion #2477 say it should, but we want a working flow on a real machine before PH-launch traffic hits the appcast). If you're seeing this line on disk, the download → EdDSA verify → installer relaunch path is healthy.
- **Auto-update was silently broken in every release ≤ build 34 (build 35).** Sparkle's `SUEnableInstallerLauncherService=true` was left enabled in Info.plist from a pre-sandbox-disable bootstrap. Daisy actually runs unsandboxed (Hardened Runtime only, no `com.apple.security.app-sandbox` in entitlements), but with the key on Sparkle took the sandboxed install path and tried to `bootstrap_look_up` the mach service `<bundle-id>-spki`. Under Hardened Runtime that requires `com.apple.security.temporary-exception.mach-lookup.global-name` in entitlements (a sandbox-only escape hatch never added to Daisy), so launchd denied bootstrap with `xpc_error=[159]` (EPERM-class policy denial). Symptom on every Sparkle prompt: "An error occurred while running the updater. Please try again later." Auto-update has never worked on any installed Daisy — every user manually re-downloaded the DMG each release. Fixed by removing `SUEnableInstallerLauncherService` from Info.plist (Sparkle now takes the unsandboxed in-process install path, which is what's documented for non-sandboxed apps) and flipping the stale `ENABLE_APP_SANDBOX=YES` in `project.pbxproj` to `NO` so the two configs stop contradicting each other. Refs: Sparkle docs at sparkle-project.org/documentation/sandboxing/, Sparkle Discussion #2477. **Users currently on builds ≤ 34 will still need to manually re-download build 35 from mydaisy.io once** — the broken Sparkle is baked into their installed `.framework`. From 35 onward auto-update works normally.
- **Crash on Record after a mic route change (build 34).** AVAudioEngine's `inputNode.outputFormat(forBus: 0)` was returning the PREVIOUS device's cached format after we'd already pinned the AUHAL to a new device via `kAudioOutputUnitProperty_CurrentDevice`. On a setup with both BuiltIn mic (48 kHz) and EarPods (44.1 kHz) plugged in, the recovery handler would read stale 44.1 kHz, then `installTap(…, format:)` would trip Apple's internal assertion `format.sampleRate == inputHWFormat.sampleRate` and crash the app with an Obj-C exception that punches straight through Swift's error handling. Fix: insert `engine.stop()` (belt-and-braces — `AVAudioEngineConfigurationChange` signals config change, not necessarily a stopped engine) and `engine.reset()` between the AUHAL device pin and the format read. `reset()` forces the input AU to re-init against the now-current `AudioDeviceID`, so the subsequent `outputFormat(forBus:)` reads fresh state. Also added a defensive CoreAudio cross-check: after the format read, query `kAudioDevicePropertyStreamFormat` on the actual bound device — if the two sample rates disagree by more than 0.5 Hz, fall to paused with a "Mic format changed — recording paused. Hit Resume to continue." toast rather than ship an installTap assertion to the user. Refs: Apple DevForum 680785, 683348.
- **MCP server bind failure under Hardened Runtime (build 34).** Log line `MCP listener failed: The operation couldn't be completed. (Network.NWError error 1 - Operation not permitted)` on every cold start. `NWError 1 = EPERM`, not `EADDRINUSE` — i.e. a policy denial, not a port conflict. macOS 14+ enforces `com.apple.security.network.server` for `NWListener.bind` even on loopback under Hardened Runtime non-sandboxed, which Daisy.entitlements was missing. Added the entitlement. MCP server now binds 127.0.0.1:54321 cleanly; the loopback + Host/Origin/CORS guard stack from 1.0.6.x is unchanged.
- **Crash on macOS 26.2 when starting a recording.** Two-part Apple bug compounded by a third-party MCP-client misbehavior. Stack always ended in `swift_task_isCurrentExecutor → swift_getObjectType → objc_msgSend` on deallocated class metadata (use-after-free in the Swift concurrency↔AppKit bridge on 26.2 — same UAF family as the documented 26.0.1 SwiftUI Button crash). Trigger: any high-frequency layout cycle would land on the bug eventually. Two contributing sources fixed: (1) SwiftUI's auto-generated `NavigationSplitView` sidebar toggle (backed by `NSSegmentedCell`) was a hot bug surface — hidden via `.toolbar(removing: .sidebarToggle)`. Daisy's sidebar stays permanently visible (Home / Library / Connections / Settings / About all anchor there anyway); divider drag still resizes. (2) MCP server gained a connection-storm circuit breaker — if a client (e.g. `mcp-remote` with broken reconnect) opens > 5 SSE streams in 30 seconds, the TCP listener tears down for 5 minutes with a loud log line. The storm was holding the runloop busy enough that the Apple UAF fired predictably during the next user action. Listener auto-restarts after the cooldown; if it keeps happening, support has a clear signal pointing at the misbehaving client rather than at Daisy itself. (3) Liquid Glass material on Record button + popover capsule ( modifier) was the THIRD UAF surface — build 32 still crashed here. Disabled `daisyGlass` shim across all macOS versions; degradation is sheen-only, painted capsule + hairline stay intact. Filed as Apple feedback.
- **Stop is snappy again on long sessions.** Pre-fix, hitting Stop on a 20+ min meeting could sit in "Stopping…" for 4 minutes — locking out the next recording while a final Whisper pass re-transcribed the full audio buffer inline. Real-world log from a 44-min Billions test session: `final_transcribe_mic: 237091ms`. The user feedback was direct: "это проблема если человеку надо со звонка на звонок пойти он не может начать новую запись". New architecture splits Stop into two stages: (1) inline `stopCapture` drains live consumers + writes a live-quality `transcript.md` from already-accumulated segments + flips `status = .finished` in well under a second — user is unblocked and can start the next recording immediately; (2) a detached task runs the final Whisper pass, re-applies speaker fingerprint matching, overwrites `transcript.md` with the polished version, then continues to summary + auto-send + audio purge as before. Live-quality transcript is real Whisper output (per-window context instead of full-session); polished version lands in tens of seconds to a few minutes later and the open SessionDetailView re-reads via `SessionStore.refresh()`. Summary always uses the polished transcript. Cooperative `Task.isCancelled` checks in `runFinalTranscribe` plus `sessionDirectory` match guards in `finalizePostStop` protect against a concurrent new-session start clobbering state mid-finalize.
- **"Clear" in the Name-the-speakers card now actually clears.** Pre-fix, clicking Clear wrote an empty `daisy_speaker_map: {}` to the transcript frontmatter, but the rendered SpeakerNameRow fields still showed the previously-typed names — and any subsequent focus blur would commit the displayed text right back into the map. Root cause: `@State private var draft` was seeded from `currentName` at init time, and SwiftUI ignores re-init of `@State` on later view updates, so the field stayed frozen with old text while the parent thought it had cleared. Added `.onChange(of: currentName)` that syncs `draft` from the parent when the field is not focused — Clear now wipes the rows visibly AND on disk.
- **Toolbar transcription-language picker is now a real two-way editor.** Pre-fix, the popover LocalePicker only wrote to `session.localeIdentifier` (per-session state). Settings → Transcription was the authoritative store, and `RecordingSession.start()` re-read it on every Record press — so a toolbar pick was silently clobbered the moment you started the next recording. Net effect a tester caught: "наоборот не работает и в итоге язык настроек возвращает язык в тулбаре". Now the toolbar pick propagates to the mode-appropriate Settings slot (`defaultTranscriptionLocale` for meeting, `voiceNoteLocale` for voice notes, `dictationLocale` for dictation) so it survives Record AND shows up next time you open Settings. The Settings → toolbar mirror was already in place (1.0.7.2) — now extended to listen on all three locale fields plus `currentMode` so picking a per-mode override in Settings while in that mode pulls through immediately.
- **Click-to-copy on the version pill.** The `v1.0.7.3` line in the sidebar footer and the `Version 1.0.7.3 (30)` line in the About header are both tappable — clicking copies `Daisy 1.0.7.3 (30)` to clipboard with a toast confirmation. Build number comes from `CFBundleVersion` (the same value Sparkle uses to compare appcast entries), so a paste into a support thread pinpoints the exact bundle the user is running rather than the marketing version alone. No visual change to either pill — `.help()` tooltip on hover is the only affordance signal, per the "no icons" constraint.
Daisy 1.0.7.2
build 29 · 27 May 2026
What's new in 1.0.7.2
- **Consent reminder at the top of the popover.** Always-on subtle privacy line above the title field: "Always get consent when transcribing others. Learn more →". The Learn-more link opens mydaisy.io/privacy. Not dismissible — this is a standing reminder that lives next to the Record button. Matches the industry-wide pattern Granola added in spring 2026 in response to EU/CA consent regulations. Daisy's positioning leans hard on "audio never leaves your Mac", but recording other PEOPLE without their knowledge is the user's responsibility, and silently shipping a meeting-recorder with zero on-screen reminder of that read as cavalier.
- **Empty-state plaque + "Draft follow-up" button when the model returned no client follow-up.** Pre-fix the Follow-up section just vanished when `clientFollowUp` came back empty — the model judging the conversation as internal-only (no external party) looked indistinguishable from a Daisy bug. Now the section renders a small explainer ("No follow-up was drafted — the model treated this conversation as internal") with a `Draft follow-up` button that re-runs JUST the follow-up generation, merging only that field back into the existing summary. Lede, sections, action items, and any manual edits stay untouched — unlike the old Re-summarize path which blew everything away. If the model again returns empty on the second pass, a toast tells the user explicitly so the no-op isn't silent.
- **"Common causes" caption under the empty-system-audio banner.** Pre-fix the banner only said WHAT ("mic only") without WHY, so users assumed it was a Daisy bug instead of a known macOS-side acoustic-loopback gap. Now the secondary line lists the three causes that cover ~95% of empty-audio sessions on the tester base: headphones (audio bypasses the loopback path), no app played sound during the meeting, or Screen Recording access was revoked mid-session.
- **Source-event title in the speaker-mapping attendee picker.** A tester saw what looked like emails from a different meeting in the rename-this-speaker picker; root cause was the wrong calendar event being bound at session-start with no on-screen indicator. The picker menu now opens with a `From: "<event title>"` section header so the user can verify the attendee list is from the expected event.
- **Inline progress banner while re-summarizing over an existing summary.** Pre-fix, hitting Re-summarize on a session that already had a summary just sat with the old content visible and no feedback for the 5–20s of LLM generation. Now a "Re-summarizing locally…" bar shows above the existing summary with the existing content dimmed to 0.45 opacity, so the user has both "yes, your click registered" feedback AND the prior summary still visible as a reference during regeneration.
- **SpeakerNameRow collapsed to a single composite field.** Removed the leading "Remote X" chip — the speaker ID now lives as the TextField placeholder so empty rows still anchor to the cluster ("Remote A" greyed out until typed over). Talk-time count and the "session only" pill moved INTO the trailing edge of the same field, replacing the separate caption row underneath. Custom plain TextField inside a focus-aware bordered container; long names scroll inside the available width rather than pushing the meta off the trailing edge. Net result: tighter one-line rows per speaker, talk-time always visible inline.
- **Tag field — popover replaced with a chevron Menu.** Pre-fix the focus-driven popover with autocomplete suggestions race-conditioned on focus loss + popover dismiss + click handler: clicking a suggestion took focus away from the TextField, which fired both the focus-out commit AND the popover outside-click dismiss AND the row's click action. Result was flicker, dropped commits, and sessions where the popover wouldn't close after a pick. Native macOS Menu has no focus dance and no dismiss race. Trade-off: lost the inline "Create '<typed text>'" affordance (Menu can't reflect live TextField state in its items) — type + Enter still creates a new tag, which is the primary path.
- **Picker option renamed "All speakers" → "Split".** Two-syllable parity with "Two sides", verb form matches the action ("split each voice into its own row").
- **Popover transcription-language picker now follows Settings.** Pre-fix `session.localeIdentifier` was baked at session-init from `settings.defaultTranscriptionLocale` and never updated again — changing the default in Settings → Transcription left the popover still showing the old language, which read as broken sync. Now the popover mirrors live changes to the default while the session is idle / finished / failed (never yanks the locale of an active recording).
- **"Open Settings…" button in PermissionsView gained explicit ink tint.** The default `.bordered` tint on Daisy's cream surface collapsed into the background — washed-out orange-on-cream looked unactionable. Inking the button gives it readable text + a dark outline without piling another orange element on a row that already accents in cinnamon (icon + Optional pill + title).
- **Long transcripts spilled out of the Transcript collapsible's rounded background.** Root cause in `SelectableTextView`: `isVerticallyResizable = true` allowed NSTextView to draw content taller than the frame SwiftUI computed via `sizeThatFits`. Three fixes: (1) `isVerticallyResizable = false` so NSTextView strictly respects the SwiftUI-assigned frame, (2) `layoutManager.ensureLayout(for:)` instead of `glyphRange(for:)` — the latter only generated glyph ranges and left the trailing paragraph un-laid-out on long transcripts, so `usedRect` reported a shorter height than what was actually drawn, (3) a 2pt bottom buffer to absorb sub-pixel rounding between the layout manager's float math and SwiftUI's integer pixel grid.
- **Swift 6 default-MainActor warning on SpeakerCentroidsFile decoding.** Daisy compiles under `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor`, which made `SpeakerCentroidsFile`'s synthesized `Decodable` conformance main-actor-isolated. `RecordingSession.makeStoredSession` is `nonisolated static` and couldn't reach the isolated init. Marked `SpeakerCentroidsFile` as `nonisolated struct` — safe opt-out for a pure data container with `let` fields and `Sendable` conformance already in place.
- **StoredSession init missing `speakerCentroidIDs` parameter on the in-memory builder path.** Build error in `RecordingSession.makeStoredSession` — the parameter was added to StoredSession when 1.0.7.1 introduced the "session only" pill, but the in-memory construction path (post-stop MCP auto-send + manual Send-to snapshot) wasn't updated. Now reads centroid IDs from `speakers.json` synchronously, falls back to empty Set when the sidecar is missing.
Daisy 1.0.7.1
build 28 · 25 May 2026
What's new in 1.0.7.1
- **Silent AVAudioFile write-death surfacing.** Pre-fix failure mode caught during a 2026-05-25 internal test: SCStream/AVAudioEngine kept delivering buffers to the live transcribe pipeline (Whisper saw the full session and produced a complete transcript), but the on-disk `.caf` archives stopped growing after some mid-session disruption. The user's `system_audio.caf` landed as 0 bytes while the transcript covered 44 minutes; `microphone.caf` was truncated to 5% of session length. There was zero indication anything had failed — the frontmatter even stamped `daisy_system_audio_status: captured` because the in-memory `hasReceivedAudio` flag was still true (it flips on the first SCK buffer regardless of disk-write outcome). 1.0.7.1 adds a render-thread frame-write counter in both `AudioRecorder` and `SystemAudioCapture`, cross-checks it against the on-disk file size and the write-error counter at stop time, and surfaces a loud toast plus a new `truncated` frontmatter state when the disk write didn't actually persist the audio. Users now know their session is partial before they let delete-after-transcription nuke the source files.
- **Per-stage timing logs across the post-stop pipeline.** Every stage between "user hit Stop" and the summary task spawning now emits a `post-stop <stage>: Xms` line plus a matching `OSSignpost` interval under category `PostStop` (subsystem `app.essazanov.Daisy`). Stages: `final_transcribe_mic`, `final_transcribe_system`, `speaker_profile_match`, `render_markdown`, `write_transcript_md`, `session_store_refresh`, then the existing `summarize` / `write_summary` / `auto_send` in the detached finalize task. Closes the silent-stages gap that made earlier "post-processing is slow" reports impossible to triage: any user can now run `log show --predicate 'subsystem == "app.essazanov.Daisy"' --last 10m --info --debug` (or System Trace → Signposts in Instruments) and see exactly which stage is eating time.
- **`daisy_mic_audio_status` frontmatter line.** Symmetric to the existing `daisy_system_audio_status`. Same four states (`off` / `captured (bytes B)` / `empty` / `truncated (...)`). Lets a folder of sessions be grepped for partial-mic incidents the same way system-audio incidents already could be.
- **`daisy_system_audio_status` frontmatter format.** Now includes byte count for `captured`/`truncated` states, e.g. `captured (4823041 B)` or `truncated (0 B on disk, 0 frames written, 3 write errors)`. Previously bare `captured` / `empty`. The four-state taxonomy (`off` / `captured` / `empty` / `truncated`) replaces the prior three-state one — `truncated` is new and names the failure mode 1.0.7 shipped without a name for.
Daisy 1.0.7
build 27 · 25 May 2026
What's new in 1.0.7
- **Settings → Transcription → "Speakers"** picker with two modes:
- **Все спикеры** (default, current behavior) — pyannote diarizes the remote stream into Bobby / Wags / Faraday / etc. Best when the meeting has distinct, well-separated voices.
- **Две стороны** — Granola-style. Mic = you, system audio = "Remote" (one label, no clustering). Best when the meeting has rapid back-and-forth or similar-sounding voices where the auto-detector over-splits clusters. Also faster (skips pyannote on system stream entirely).
- **Settings → Transcription → "Suppress acoustic echo"** toggle, default **on**. When user plays meeting audio through speakers (not headphones), the microphone re-captures the same audio and Whisper transcribes every line twice — once correctly attributed to "Remote", once incorrectly attributed to the user. The new dedup pass walks every mic-side segment, checks for any system-side segment within ±2 seconds whose text matches by >0.8 normalized Levenshtein similarity with ±20% length match, and drops the mic segment if matched. Sequential matches (3+ in a row) are treated as confirmed echo and dropped aggressively; isolated single-match segments are kept (legitimate "you quoting what the other person said"). Honest 90% mitigation — Apple's voice-processing AEC at the audio-graph level would be 99%, but it requires rebuilding the AVAudioEngine + ScreenCaptureKit capture pipeline which we're not doing pre-PH. Headphones still solve at the source.
- **Manual "Re-cluster speakers" button in SessionDetailView** — addresses the third issue from the 2026-05-25 Billions test (live streaming diarization fragments the same voice into Remote A / Remote D / Remote G across 10-30s chunks because there's no cross-chunk linking). Original plan was a silent auto-fire after summary; reframed as a user-triggered button to keep blast radius low — if the global pass has bugs, only sessions where the user explicitly clicks are affected, not every saved session. Background work + flag wiring (`globalReclusterAfterStop` setting in AppSettings) reserved in 1.0.7 so the migration is clean when the button lands.
Daisy 1.0.6.12
build 26 · 25 May 2026
What's new in 1.0.6.12
- **Voice notes no longer inherit the title of an old meeting.** Tester case: a calendar-bound meeting named "Pilik's Birthday" finished and saved days earlier; a fresh voice-note hotkey hit then created a new session that was saved under the same "Pilik's Birthday" title (instead of "Voice note 2026-05-25 12:34"). Under the hood `reset()` was clearing the meeting binding, folder and mode but not the title — so the next session's title-generation step found a non-empty value and skipped regeneration. Title is now cleared on reset and re-generated on every start from the new session's mode + calendar binding.
- **Notion row captions referenced "below" after the config moved.** When Notion settings moved into a modal sheet (opened by the gear button) in 1.0.5.4, three captions in Settings → General → Storage kept saying "Configure below" / "Configure Notion settings below first" — pointing at something that wasn't there any more. Rewritten to point at the gear.
- **"Other side wasn't captured" banner is shown once, not on every session.** 1.0.6.11 added a three-paragraph orange banner above the transcript on any session whose system audio capture stayed empty. For users on the macOS 26 SCStream regression that's effectively every meeting — so opening the library after the update gave you a wall of identical paragraphs. The banner is now a single-line "Other side wasn't captured this session — mic only" with a "Got it" dismiss that hides it for good on this device. The detailed explanation (Bluetooth output / Tahoe regression / how to enable mic diarization) still fires once as the post-stop toast, which is the right moment for it.
- **Settings → Storage section copy + layout pass.** The retention picker now carries a leading icon for row-rhythm parity with Sessions folder / Clear / Notion rows. The retention caption and the Storage footer drop the `.caf` filename — engineering noise users don't need to read. The "Clear audio cache now" row title is now "Clear all audio now" (cache is a dev word), the count next to it pluralises properly ("1 recording" / "27 recordings" instead of "1 file(s)"), the trailing button drops its ellipsis (no further input — the confirm alert is a yes/no, not a picker), and its disabled state no longer renders muddy peach against the cream surface (was tinted by `role: .destructive` even when off — fixed). Confirm alert says "Delete all audio recordings?" with a "Delete" button and a message that no longer leaks the `.caf` filenames. The redundant `Divider()` between Clear and Notion is gone — Form's row separators are enough on their own.
- **Permissions tab is calmer.** Every row caption was a 2-3 sentence paragraph repeating "Required." or "Optional." prefixes that the badge already said — now each row reads as a single short sentence naming why the permission exists. The privacy explainer ("Daisy works entirely on-device…") moved from the bottom of the page up to the section header, so the reassurance arrives before the user clicks any Request button, not after.
- **Bigger Start button in the sidebar.** Vertical padding on the record capsule bumped 8 → 14pt (+12pt total) so the primary action of the sidebar reads as the unambiguous primary action of the sidebar.
- **About page subtitle pulls in the privacy explainer.** Previously the page had two value-prop statements ~600pt apart — one short subtitle under the title and a longer "everything stays on-device" paragraph at the very bottom. Merged into one subtitle directly under the Daisy mark so the on-device + opt-in story lands before you scroll five rows of links instead of after.
- **New "Delete after transcription" retention option, set as the fresh-install default.** Settings → Storage → Delete audio after now has a fifth option at the top of the picker: "After transcription". When set, Daisy purges the raw `.caf` for each session the moment the post-stop pipeline lands transcript + summary on disk — no 24h+ wait. New installs get this by default; the existing "24 hours / 7 days / 30 days / Keep forever" picks all preserve their behaviour, and anyone upgrading who never opened Settings → Storage stays on "Keep forever" so yesterday's recordings don't surprise-delete. Triggered by a competitor scan (Granola, Fireflies, Otter, Fathom, Read.ai, Circleback) — Granola's "audio doesn't persist" posture is table stakes in this category now, and the new mode matches it while keeping the longer-retention options for users (legal, journalists, anyone re-summarizing later) who need the audio file. Per-session purge is gated on summary-success so a failed AI call doesn't strand the user with a transcript-only artifact they can't redo.
- **App-wide banner / chip / colour unification pass.** Colorist + UI shape audits flagged that "row-level banners" were rendered four different ways across Home and SessionDetail (different fills, borders, button widths, button styles), that ConnectionsView had five raw `.green` / `.red` / `.orange` leaks bypassing the brand palette (incl. a "Beta" pill in recording-orange that read as "this connector is currently capturing"), and that the in-card section chrome split radius 8 vs 10 between AboutView and SessionDetailView for the same role. Result: every row-level banner (`permissionsAttentionBanner`, `connectCalendarCTA`, `deniedCalendarCTA`, `destinationsHint`, `acousticLoopbackBanner`, `actionBanner`) is now one shape — cinnamon-or-status chip, 0.20 fill + 0.20 strokeBorder, 12/10 padding, radius 8, CTA pinned at `minWidth 88`. Five Google Calendar Connections raw colours mapped to `daisySuccess` / `daisyError` / `daisyWarning` / `daisyAccent`. `speakerMappingSection` promoted to the radius-10 section-card spec to match AboutView. AboutView icon column 22 → 18 for cross-surface row-rhythm parity with Settings + Permissions. Empty calendar-day state now matches `UpcomingEventRow` padding so the day empties out with no layout jump. Full audit reports in vault `business/research/2026-05-25-daisy-color-audit.md` + `…-ui-shape-audit.md`.
Daisy 1.0.6.11
build 24 · 23 May 2026
What's new in 1.0.6.11
- **System audio capture on macOS 26 (Tahoe).** Early Tahoe builds (26.0.x) regressed ScreenCaptureKit loopback for our default config — `SCStream` attached without error but never delivered a single audio buffer, even on built-in speakers with no Bluetooth in the picture. Switched to mono single-channel + dropped `excludesCurrentProcessAudio` on macOS 26+ (the two knobs that match the workaround pattern reported on Apple's developer forums for Tahoe SCStream audio bugs). macOS 14 / 15 keep the year-validated 2-channel config. Filed Apple Feedback in parallel — long-term fix lives on their side.
- **Misleading "Likely BT output" warning.** When system audio came back empty, the toast and log claimed it was probably Bluetooth — confusing for users without Bluetooth where the real cause was the Tahoe SCStream issue above. Rewritten to mention both causes neutrally.
- **"Other side wasn't captured" banner in session detail.** When a session's system audio is empty, the transcript view now shows an explicit banner explaining why every line ends up labelled with your name (the microphone picks up the other side acoustically through your speakers, so it's the only audio source Daisy has), plus two paths forward: enable Settings → Transcription → "Diarize microphone too" to split voices on the existing recording, and switch to built-in speakers for next time.
Daisy 1.0.6.10
build 23 · 23 May 2026
What's new in 1.0.6.10
- **Google Calendar is available again as a Beta connection.** Connections → Calendar lets you connect Google Calendar (read-only, scope `calendar.readonly`) for session naming, attendee prefill, and auto-start / auto-stop around meetings — same surface Apple Calendar already powers via EventKit. While Google's OAuth verification is in flight, the consent screen will show an "App isn't verified" warning; click Advanced → Continue to proceed. The `Beta` badge and the in-tab notice come off the moment Google approves the verification request.
Daisy 1.0.6.9
build 22 · 22 May 2026
What's new in 1.0.6.9
- **Friendlier start cues.** Meeting and Resume now use the same warm `Purr` tone as dictation — the old `Tink` read in macOS UX as an edge-of-slider / unhandled-key ping, so every recording start felt like a mis-press. Voice notes keep `Pop` so a quick tap still sounds distinct from longer recording modes.
- **Settings tabs back to native macOS chrome.** A defensive workaround that replaced the Settings tab bar with a custom HStack on macOS 26 is reverted. The crash that prompted it correlates with low disk + a partially-downloaded transcription model, not with the segmented control itself.
- **Audio retention default is now 24 hours on fresh installs**, with a simpler choice in Settings → General → Storage: 24 hours / 7 days / 30 days / Keep forever. Existing users keep whatever they had — if you never touched the retention picker, your audio is still kept forever until you explicitly change it.
- **Clear audio cache now.** Settings → General → Storage gets a "Clear audio cache" row that shows the current `.caf` footprint across every session and a one-click destructive purge with a confirm alert. Transcripts, summaries and screenshots are never touched; only raw audio is removed.
- **Whisper download checks free disk space first.** If the home volume has less than 2 GB free, the model download stops immediately with a concrete error ("need 2.0 GB free, have 0.8 GB") instead of silently wedging at 100% downloaded.
Daisy 1.0.6.8
build 21 · 22 May 2026
What's new in 1.0.6.8
- **Meeting and Resume now use the same `Purr` tone as dictation.** The old `Tink` start sound reads in macOS UX as an edge-of-slider / unhandled-key ping — every recording start felt like a mis-press. Voice notes keep `Pop` so a quick tap still sounds distinct from the longer recording modes.
Daisy 1.0.6.7
build 20 · 22 May 2026
What's new in 1.0.6.7
- **Settings no longer crashes on macOS 26 (Tahoe).** Early Tahoe builds (26.0.1) crash inside Apple's `DesignLibrary` framework while sizing the segmented tab control SwiftUI puts in the Settings window toolbar — `SerialExecutor.isMainExecutor` in `SystemSegmentedControl` hits a garbage witness pointer and the app dies with `SIGBUS`. On macOS 26 Daisy now renders its own tab bar instead of routing through that Apple code path. macOS 14 / 15 keep the native `TabView` (the bug doesn't exist there).
Daisy 1.0.6.6
build 19 · 22 May 2026
What's new in 1.0.6.6
- **Mode-aware start sound.** Pressing Fn for dictation used to play the same `Tink` cue as a meeting start — a system sound macOS also uses when you bump the volume ceiling or hit an unhandled key, which made every dictation tap feel like a mis-press. Now each mode has its own cue: meeting stays on `Tink` (the long-session "armed" signal), voice notes use a soft `Pop`, and dictation uses `Purr` — a short, warm "listening" tone. Stop sound is unchanged.
Daisy 1.0.6.5
build 18 · 22 May 2026
What's new in 1.0.6.5
- **Cleaner Library list rows.** Removed the system-audio speaker icon from session rows for parity with the session detail header (also dropped in 1.0.6.4). The session title already communicates whether a recording is a meeting; the duplicate icon was visual noise.
Daisy 1.0.6.4
build 17 · 22 May 2026
What's new in 1.0.6.4
- **Daisy is now fully open source under Apache 2.0.** Switched from BUSL 1.1 — no commercial-use carve-outs, no time-bombed conversion clock, standard OSI-approved permissive licence. Read every line, build from source, fork, ship your own variant.
- **Voice notes now read as voice notes.** Sessions recorded via the voice-notes hotkey were titled "Meeting yyyy-mm-dd hh:mm" regardless of mode. Title now reflects the actual recording mode — "Voice note …", "Dictation …", "Meeting …".
- **Cleaner session header.** Removed the AUTO/EN/RU locale chip (already controlled in Settings → Transcription) and the system-audio speaker icon (the user already knows they recorded a meeting — the title literally says so). Header is now just date · duration · tag.
- **Settings → Profile.** Renamed the "You" section to "Profile" to match the macOS Settings convention.
Daisy 1.0.6.3
build 16 · 22 May 2026
What's new in 1.0.6.3
- **Display name now wins in transcripts.** When mic-side diarisation tagged a solo recording's segments with cluster label "A", the transcript showed "Speaker A" instead of the name set in Settings → General → You. Display name now overrides the cluster label for all mic-stream segments — your name shows up wherever you spoke. Per-speaker disambiguation on multi-speaker mic streams still works via the Detail-view speaker-map UI.
- **No false "only your voice" warning during voice notes.** Voice notes and dictation are mic-only by design — the user is the only voice that matters. The status pill was incorrectly showing a "couldn't reach the other side" warning during those recordings. It now hides itself for non-meeting modes.
- **Sidebar capsule polish.** Status row moved below Stop button — tap target first, info second. Copy rewritten in plain English: "Recording both sides" / "Only your voice — Screen Recording is off" / "Only your voice — couldn't reach the other side". All three states now share a consistent tinted-capsule shape so the eye lands on the same place regardless of state.
Daisy 1.0.6.2
build 15 · 21 May 2026
What's new in 1.0.6.2
- **Screen Recording permission button.** Clicking "Allow Screen Recording" from Settings or onboarding sometimes did nothing on macOS 14+ (`CGRequestScreenCaptureAccess` is unreliable post-Sonoma — known TCC daemon issue). Now falls back to opening System Settings → Privacy → Screen Recording directly. Status correctly reflects "Denied" after first attempt instead of looping on "Request".
- **Onboarding flow — 6 steps instead of 4.** Added explicit Accessibility step (needed for dictation auto-paste via ⌘V) and a Hotkeys step where you assign global shortcuts for Meeting / Voice notes / Dictation in one screen, with colour dots matching each mode's widget centre. Focus observer auto-refreshes permission status when you return from System Settings, so granted permissions show up without an extra click.
- **Whisper model loading UX.** First-record now shows a one-shot toast ("Setting up transcription model — first run only, about 1–3 minutes"). Widget centre pulses amber (instead of plain white) while loading. Tooltip surfaces real progress: "Downloading transcription model · 47%". Onboarding Done step shows a progress row reading `WhisperEngine.state` directly, so the model finishes downloading while the user reads "You're set".
Daisy 1.0.6.1
build 14 · 21 May 2026
What's new in 1.0.6.1
- **History sidebar — tag filter moved up.** Tag selector now lives in the search row next to the title, sharing the capsule region with search and tags. Removed the manual reload icon (the store auto-refreshes on focus + file changes). Padding bumped between search and folder chips so the row stops feeling cramped.
- **Tag editing in session header — Notion-style autocomplete.** Click the tag field and a popover opens with every tag you've used before, filtered by what you type. Click a row to apply, or hit Enter to create a brand-new one. A "Remove tag" row clears the current value with one click. No more chevron — the field is the affordance.
- **Settings → Transcription — Diarization moved above Language.** Diarization is a structural "how is the audio interpreted" choice (same family as Model). Language is content-level. Order now reflects that. Diarization caption shortened to one line; the long version moved to the footer.
- Dead-code sweep: removed `ClientSuggestion.swift` typealias stub (post-rename from 1.0.5.2), neutered `AudioRecorder.prewarm()` method (no callers since the 1.0.5.1 crash), unused `refreshRotation` state in LibraryView. Two stale one-off docs (`_release-1.0.4-checklist.md`, `qa-1.0.4.md`) cleared from `scripts/`. DaisyUITests target dropped — was Xcode-template scaffold, never actually run; `DaisyTests` regression suite stays as the audit-recommended pure-function lock.
Daisy 1.0.6
build 13 · 21 May 2026
What's new in 1.0.6
- **Tag your sessions.** Sessions now carry an optional free-form tag — auto-suggested from the dominant external attendee domain on a calendar-bound meeting ("Mediacube", "Acme") — and surface as a chip next to the title. History sidebar got a tag selector (single-select dropdown) that intersects with the folder filter. The detail-view tag editor offers a chevron dropdown with every tag you've used before, so you pick "Mediacube" once instead of typing it ten times and risking three different buckets.
- **Your name in transcripts.** Settings → General → You — type your name once, and every line you speak shows up as `[Egor]` instead of `[Me]`. The summarizer picks it up too, so multi-person meeting recaps can address you by name in sentences like "Maria asked Egor about pricing".
- **Delete old audio automatically.** Settings → General → Storage now has a "Delete audio after" picker — 7 / 30 / 90 / 180 / 365 days, or Keep forever (default). Removes raw `.caf` files for sessions past the cutoff; transcripts, summaries, and screenshots stay untouched. Audio dominates the on-disk footprint; this lets you trim it without losing the meeting record.
- **Auto-start banner with Stop action.** When a calendar event auto-starts recording, Daisy posts a macOS banner — "Recording started — Q3 review" — with an inline Stop & save button so you can bail if the wrong meeting got picked up.
- **Auto-stop banner confirmation.** When the calendar event ends and Daisy auto-stops + saves the recording, you get a banner letting you know — Settings → General → Notifications lets you toggle each banner class independently.
- **Microphone-side diarization (opt-in).** Settings → Transcription → Diarization → "Diarize microphone too". Useful when remote participants are heard through your speakers (in-room playback) instead of being captured via system-audio loopback — turns the all-"Me" transcript into per-speaker labels.
- **Notifications row in Permissions.** Settings → Permissions surfaces the macOS Notifications permission alongside Microphone / Accessibility / Calendar / Screen Recording.
- **⌘+Delete in History.** Standard macOS "move to trash" combo. Plain Backspace still works. Enter in the confirmation dialog now triggers Delete (Esc cancels).
- **Empty system-audio warning.** If you finish a longer meeting with system audio enabled but no audio actually captured (almost always Bluetooth headphones — macOS refuses to loop those through Screen Capture), Daisy surfaces an explicit warning at the end of the session and writes `daisy_system_audio_status: empty` into the transcript frontmatter for debugging.
- **Settings → General is calmer.** Calendar / Auto-start / After Stop merged into a single "Meetings" section — same toggles, fewer headers, one mental model. Shortcuts: per-row hints instead of one wall of footer text.
- **Settings → Transcription mirrors Summary.** Model status (Ready badge + Reload) sits inline under the Model picker, not in its own section. Section header on the LLM block renamed to "Provider".
- **Notion configuration moved.** No longer a tab in Connections — it's now a row in Settings → General → Storage right next to the local sessions folder (same logical category: "where finished meetings go"). A gear icon next to the row opens a proper modal sheet with secret, parent ID, page/database toggle, and Test connection.
- **Connections sidebar is leaner.** Auto-routing + MCP server only. Auto-routing is the default tab now.
- **Permissions tab quieter.** Removed the per-row "Granted / Not requested" status pills — icon colour on the left and action button on the right already communicate state.
- **Follow-up draft is now 2–4 short paragraphs.** Replaces the previous "one big paragraph" output: opener, recap, next steps with owner + timeline, optional sign-off. Existing sessions stay as-is; hit the sparkles button to re-summarize an older one in the new shape.
- **Copy markdown copies the whole document.** The toolbar Copy button used to grab transcript text only; now it assembles Title → Summary → Sections → Next actions → Follow-up → Transcript into one paste-ready markdown blob.
- **Dictation text flows naturally.** Segments now join with spaces, not newlines, so pasted dictation reads as continuous prose.
- **Auto-routing templates removed for now.** Attio / Webhook / Linear preset templates are gone until they're verified end-to-end against the real upstream services. Blank integration is the only safe path; Add-integration is a direct button instead of a menu.
- **MCP connection no longer hangs after the first AI request.** The Daisy MCP server's SSE stream had no keepalive — once idle for ~30 seconds (you finishing one Claude prompt and starting another), the loopback socket would silently go half-open and the next prompt would hang for 3+ minutes. Server now emits an SSE comment-frame ping every 15 seconds for the lifetime of the connection.
- **Claude Desktop reload after a hung session works correctly.** Stale state from a previous hung MCP call no longer poisons the next Claude launch with "not valid MCP server configurations: daisy".
- **"Add to Claude Desktop" button writes a working config on the first try.** The shipped snippet now passes `--transport sse-only --allow-http` to `mcp-remote`. Without them, the bridge tried Streamable HTTP first and refused plain HTTP on loopback. Manual configs need the same two flags — see [docs/mcp](https://mydaisy.io/docs/mcp).
- **Follow-up draft is selectable again.** A layout issue was eating mouse hit-events over the follow-up text on macOS 26 — restructured the layout so the text occupies the full content width with unobstructed selection.
- **Summarizer no longer inverts customer-answer polarity.** When a customer answered "no, our staff is all in Belarus" to a sales rep's diagnostic question, the model used to recast that as "opportunity for future contractor payouts" — flipping a constraint into an opportunity. Prompt now explicitly forbids polarity inversion and gives the model linguistic cues for inferring asker vs. answerer when diarization is missing.
- **Calendar settings now read live permission state.** Pre-1.0.6 the Calendar toggles in General could stay disabled even after granting Apple Calendar in System Settings, because the gating read a stale UserDefaults cache. Now it reads the same source the Permissions tab shows.
- **Empty row in the Storage section.** Collapsing the old Notion DisclosureGroup left a visible empty row; replacing it with a modal sheet fixes it.
- Sessions tagged in 1.0.5 (which used a `client` concept) read transparently as tagged sessions in 1.0.6 and are rewritten with the new `tag` key on the next manual edit. Nothing to do.
- New [docs/mcp page](https://mydaisy.io/docs/mcp) — step-by-step setup for Claude Desktop / Cursor / Cline, plus 16 example prompts grouped by use case (lookup, cross-session synthesis, drafting, analytics) and a troubleshooting block.
Daisy 1.0.5.1
build 11 · 21 May 2026
What's new in 1.0.5.1
- **App no longer crashes on launch.** 1.0.5 introduced a launch-time call to `AVAudioEngine.prepare()` intended to warm up the audio graph and make the first dictation feel snappier. On macOS 26.2 that call throws an ObjC exception (`AVAudioEngineGraph::Initialize`) before microphone permission has been granted — and the exception aborts the process before SwiftUI even mounts. If you installed 1.0.5 and saw Daisy bounce in the Dock, this is the fix. Removed the launch-time prewarm; first-record latency goes back to where it was in 1.0.4.
Daisy 1.0.5
build 10 · 21 May 2026
What's new in 1.0.5
- **Your name in transcripts.** Settings → General → You — type your name once, and every line you speak in a meeting transcript is labelled with it instead of the generic "Me". The summarizer sees your name too, so it can address you directly in multi-person meeting recaps ("Maria asked Egor about pricing") instead of using a placeholder.
- **Client tagging.** Sessions can now carry a free-form client tag — Daisy auto-suggests one from the dominant external attendee domain on a calendar-bound meeting (e.g. three people from `@mediacube.io` → suggested tag "Mediacube"). The tag is editable inline in the session header, persists into the transcript frontmatter as `daisy_client:`, and powers a new client filter row in History with live counts per tag plus an "Untagged" bucket.
- **Auto-start notification.** When a calendar event auto-starts recording, Daisy now posts a macOS banner — "Recording started — Q3 review" — with an inline Stop & save action so you can bail out instantly if the wrong meeting got picked up.
- **Auto-stop notification.** Confirmation banner when the calendar event ends and Daisy auto-stops + saves the recording. Toggle independently in Settings.
- **Per-class notification toggles.** Settings → General → Notifications gives you individual switches for "Recording started", "Meeting ended — saved", and the existing "Long silence" prompt.
- **Microphone-side diarization (opt-in).** Settings → Transcription → Diarization → "Diarize microphone too". Useful when remote meeting participants are heard through your speakers (in-room playback) instead of being captured separately via system-audio loopback — turns the all-"Me" transcript into per-speaker labels.
- **Empty system-audio warning.** If you finish a meeting longer than a minute with system audio enabled but no audio actually captured (almost always Bluetooth headphones — macOS refuses to loop those into Screen Capture), Daisy now surfaces an explicit warning toast at the end of the session and writes `daisy_system_audio_status: empty` into the transcript frontmatter for grep-able diagnostics.
- **⌘+Delete in History.** Standard macOS "move to trash" combo. Plain Backspace still works. Enter in the confirmation dialog now triggers Delete (Esc cancels).
- **Notion configuration moved.** It now lives in Settings → General → Storage right next to the local sessions folder — they're the same logical category ("where Daisy sends a finished meeting"). The advanced fields (secret, parent ID, Page/Database picker, Test connection) collapse into a single DisclosureGroup so the row isn't intimidating for users who don't wire Notion up. Connections sidebar is leaner — MCP server + Auto-routing only.
- **Permissions tab — quieter.** The redundant "Granted / Not requested" status pills on every row are gone. State is communicated by the icon colour on the left and the action button on the right (Request / Open Settings… / Revoke…). Section-level "Required permission missing" header warning stays.
- **Follow-up draft is now 2–4 short paragraphs.** Replaces the previous "one big paragraph" output — opener, recap of what was agreed, next steps with owner + timeline, optional sign-off. Existing sessions stay as-is; hit the sparkles button to re-summarize an older one in the new shape.
- **Copy markdown copies the whole document.** The toolbar Copy button used to grab transcript text only; now it assembles Title → Summary → Sections → Next actions → Follow-up → Transcript into one paste-ready markdown blob. Status toast tells you what scope went on the clipboard.
- **Dictation text flows naturally.** Dictation segments were joined with newlines, making the pasted result look shredded. They now join with spaces so consecutive Whisper chunks read as continuous prose, with the model's own punctuation handling sentence boundaries.
- **Follow-up draft is selectable again.** A layout issue was eating mouse hit-events over the follow-up text on macOS 26, so even though `.textSelection(.enabled)` was set, you couldn't mouse-select and ⌘C the draft. Restructured so the text occupies the full content width with unobstructed selection while the inline copy button still sits in the corner.
- **Summarizer no longer inverts customer-answer polarity.** When a customer answered "no, our staff is all in Belarus" to a sales rep's diagnostic question, the model used to recast that as "opportunity for future contractor payouts" — flipping a constraint into an opportunity. Prompt now explicitly forbids polarity inversion and gives the model linguistic cues for inferring asker vs. answerer when diarization is missing.
- New per-class macOS notification surfaces (`AutoStartNotification`, `AutoStopNotification`) with shared registration + delegate routing. Action button taps come back through Foundation NotificationCenter the same way `SilencePromptNotification` already worked.
- `ClientSuggestion` pure-function module — `extractDomain` / `isFreeMailDomain` / `normalizeDisplayName` / `suggest(from:)`. Hardcoded free-mail blocklist (gmail / outlook / icloud / proton / fastmail / yandex / mail.ru / et al.) and subdomain stripping (`mail.acme.io` → `Acme`). No new entity, no CRM table — tag lives on the session.
- `DaisyMeeting.attendeeEmails: [String]` projected from both EventKit and Google Calendar event payloads. Drives the client auto-suggestion and is reserved for future "remember this client's contact emails" features.
- `StoredSession.client: String` round-trips through `daisy_client:` frontmatter. Search box now matches against it ("mediacube" finds every session tagged with that client).
- `SessionStore.setClient(_:for:)` writes the tag back to disk via the same upsert-frontmatter machinery `moveSession` uses for folders.
- `Transcriber.diarize:` init parameter — defaults to `true` for system-audio source, `false` for microphone (overridden by `settings.diarizeMicrophone`). Decouples the diarization decision from the source enum without forcing a Transcriber→Settings dependency.
- `TranscriptSegment.speakerLabel(displayName:)` overload accepts an optional override for the user's own voice. Pass `settings.userDisplayName` to substitute "Me" → real name; pass nil/empty to keep the legacy label. System-source labels are unaffected.
- `AudioRecorder.prewarm()` — `engine.prepare()` at app launch caches the HAL audio graph so the first user-initiated record click doesn't pay the cold-start tax. Called from `RecordingSession.init`. No TCC prompts, no recording-light.
- `RecordingSession` post-session check: if `captureSystemAudio = on`, `currentMode = .meeting`, `elapsed > 60s`, and `systemAudio.hasReceivedAudio = false` → warning toast + frontmatter status.
- `hasCapturedSystemAudio: Bool` proxy on RecordingSession so MarkdownExporter (and other read-only consumers) can persist the system-audio capture outcome without widening visibility of the private `systemAudio` child.
Daisy 1.0.4
build 9 · 21 May 2026
What's new in 1.0.4
- **Mid-meeting headphones don't kill your recording anymore.** Plugging wired headphones (USB or 3.5mm) into the Mac during a session used to silently drop both microphone and the other side's audio — the timer kept ticking but nothing was captured. The route-change recovery now always re-binds the audio engine to a concrete device (it was short-circuiting when you had "System default" picked), and a watchdog catches any case where the recovery looks fine but no audio actually arrives — you get a Resume toast instead of an empty file.
- **Calendar auto-stop fires when you start the meeting manually.** With "Stop when the event ends" turned on, Daisy used to silently ignore the setting if you'd hit the record hotkey yourself before the calendar tick auto-started the session. Now the session links itself to whichever meeting is currently running and the auto-stop arms correctly.
- **Settings → General Calendar section reflects live access state.** Previously the toggles could stay disabled even after granting Apple Calendar in System Settings — the gating read a stale local cache. Now it reads the same source the Permissions tab shows, so a fresh grant unlocks everything immediately when you tab back to Daisy.
- **Calendar settings moved from Connections to Settings → General.** Where you turn on auto-start, auto-stop, and "show next meeting in the menu bar" now sits next to the other recording behaviour preferences. Granting / revoking calendar access still lives in Settings → Permissions, where you'd expect a system-level privacy toggle.
- **DMG window background cleaned up.** Removed a decorative daisy graphic that was duplicating Daisy's actual app icon — installer window now shows just the app, the Applications shortcut, and the arrow between them.
- New CoreAudio property listener on `kAudioHardwarePropertyDefaultInputDevice` complements the existing output-device listener — catches mic-side route changes that `AVAudioEngineConfigurationChange` silently misses (output-only headphones on macOS 14+).
- `Logger(category: "AutoStop")` separates calendar-bound stop wiring from the general `Session` log. Every early-return in `scheduleAutoStopIfNeeded()` now writes a line — pre-1.0.4 four distinct failure modes all looked the same from Console.app (i.e. silent).
- `release.sh` now runs a sanity check on the build number before any archive work — fails in under a second if the supplied build is `<=` the last published `<sparkle:version>` in `appcast.xml`. Catches the class of bug that shipped 1.0.3 build 6 alongside an already-published 1.0.2 build 7, which Sparkle correctly treated as a downgrade.
- `AudioRecorder.applyPreferredInputDevice(uid:)` no longer short-circuits on `uid.isEmpty`. The AUHAL is always pinned to a concrete `AudioDeviceID`, resolved via `AudioInputDevices.systemDefaultInputID()` when the user picked "System default". Stops the silent-recording-after-route-change failure mode.
- Recovery watchdog arms after every successful `engine.start()` in route-change recovery; falls to `.paused` (with a toast) if no audio buffer arrives within 5 seconds. Bridges from the audio render thread via an `OSAllocatedUnfairLock`-backed timestamp box.
- `RecordingSession.bindCurrentMeetingIfPossible()` links a manually-started session to a calendar event that's currently in its `-120 s … +30 s` start window, so the auto-stop pipeline works the same whether the session began from the hotkey or the calendar tick. Re-runs on `resume()` as a late-binding safety net.
Daisy 1.0.3
build 8 · 20 May 2026
What's new in 1.0.3
- **Dictation mode** — hold a hotkey to talk, release to paste. The transcript lands directly into whatever app is in front (chat, email, doc) via the system clipboard, the same way Wispr Flow works. No need to switch back to Daisy and copy-paste manually. Hotkey is configurable in Settings → General; default is unbound so existing users don't conflict with their muscle memory.
- **Voice notes mode** — one-tap recording for the quick "remind me later" thoughts that don't belong in a meeting transcript. Separate hotkey, separate session list section. Tap once to start, tap again to stop.
- **Three recording modes are now visually distinct.** The center of the floating widget colour-codes the active mode: orange for meetings (the macOS mic indicator hue), lilac for dictation, coral for voice notes. At a glance you know which keystroke you just hit.
- **Fn key as hotkey option** — for users who like the iPhone-style "press the globe key to dictate" gesture. Daisy can bind to Fn alone or in combination with another modifier.
- **Permissions dashboard in Settings → Permissions** — single page showing live status of Microphone, Accessibility, Calendar, and Screen Recording. Each row has a Request button if not yet granted, an Open Settings… deep-link if denied, and a Revoke… link if granted. Status updates automatically when you come back from System Settings, no relaunch needed.
- **Home banner** when a required permission is missing — mic or accessibility off means recording or auto-paste won't work, and the banner now says so directly with a one-click Fix button that opens the right pane.
- **Floating widget shows at app launch** even before you start recording, so you have a draggable handle to position it where you want.
- **Connect Apple Calendar button no longer fails silently.** Under Hardened Runtime on macOS 14+ the system was rejecting the TCC prompt request before showing the dialog, because the corresponding entitlement was missing. Daisy never appeared in System Settings → Privacy → Calendars and no amount of clicking Connect did anything. Fixed by adding the required entitlement to the signed bundle.
- **Microphone capture returning silent buffers after a TCC reset.** Same class of bug as Calendar above — Hardened Runtime requires the `audio-input` entitlement for the mic prompt to fire on macOS 14+. Without it the first call to `requestAccess` was silently denied, and subsequent recordings produced zero transcription segments.
- **Voice note hotkey behaviour reverted to single-tap toggle** (it briefly was hold-to-talk, which felt wrong for the "leave myself a quick thought" use case). Dictation stays hold-to-talk; only voice notes are toggle.
- **HotkeyRecorder now captures the Fn / globe key** correctly. Previously Fn alone produced no `.keyDown` event and the recorder ignored the press.
- **Globe key shows as an SF Symbol** in the hotkey label instead of the 🌐 emoji.
- **App Sandbox is now off.** Dictation needs `CGEvent.post(⌘V)` against the frontmost app, which App Sandbox blocks by design. Daisy is distributed via signed + notarised DMG with Hardened Runtime — same security posture as Wispr Flow, MacWhisper, Superwhisper. Mac App Store distribution is not planned.
- Microphone, Calendar, and Network entitlements are now declared explicitly in the entitlements file (required under Hardened Runtime regardless of sandbox state). If a future build adds Contacts / Photos / Reminders / Location, the matching entitlement has to be added too — there's a comment in `Daisy.entitlements` listing the table.
- `SystemPermissions.shared` — single observable façade over `AVCaptureDevice` / `EKEventStore` / `AXIsProcessTrusted` / `CGPreflightScreenCaptureAccess`. Auto-refreshes on `NSApplication.didBecomeActiveNotification` so the UI reflects external changes in System Settings without a relaunch.
- Diagnostic logging in `CalendarService.requestAccess` under the `Calendar/Perm` os_log category — surfaces pre-call / post-call status + any `requestFullAccessToEvents` throw, so support cases like the one we just hit are 30 seconds of `log stream` to diagnose instead of hours of guesswork.
- Screen Recording request now uses `CGRequestScreenCaptureAccess()` (macOS 11+) which actually shows the system prompt and registers the app in Privacy → Screen Recording. Previously the button just opened System Settings, where Daisy wasn't in the list yet.
Daisy 1.0.2
build 7 · 19 May 2026
What's new in 1.0.2
- Mic switch mid-recording (AirPods plug/unplug, USB mic, sound output re-route) no longer silently loses audio. If the new device has a different sample rate or channel count, Daisy continues writing into a second file alongside the original instead of dropping every buffer into a hidden error counter.
- Two back-to-back calendar meetings used to silently record into one session with the first meeting's title. Now the first meeting saves and the second starts fresh as its own session.
- Calendar event auto-stop was technically wired up but never actually firing because the meeting binding got cleared during session start. Fixed — auto-stop now arms correctly when bound to a calendar event.
- Auto-stop on calendar event end is on by default for new installs. If you explicitly turned it off, your choice is preserved.
- "Apple Calendar — Connected" row no longer says "Connected" twice; the green badge in the section header already carries that.
- Daisy warns within 30 seconds if system audio capture is on but no sound is reaching it. This usually means Bluetooth headphones — macOS can't loop those back into Screen Capture. Use built-in speakers, wired headphones, or BlackHole if you need the remote side recorded.
- One-time heads-up at session start if your default output is Bluetooth, so you can switch before the meeting begins.
- If you change output device mid-session, Daisy rebuilds the system audio capture against the new default and surfaces a toast that it's continuing.
- When a meeting app (Zoom, Meet, Teams, Webex) launches while Daisy is already recording, you see a clear toast instead of silent no-op.
- Empty `system_audio.caf` placeholder is created at session start, so post-session debugging can tell "capture never armed" from "capture received nothing".
Daisy 1.0.1
build 5 · 19 May 2026
What's new in 1.0.1
- Auto-updates are live. Daisy now checks mydaisy.io daily and lets you install new versions in one click from Settings → Updates.
- Connections moved into the sidebar — Calendar, Notion, MCP server, and auto-send destinations now share one home next to History.
- Microphone hot-swap: Daisy detects when the input device changes mid-recording (AirPods reconnect, USB mic unplugged) and keeps capturing instead of dropping out.
- Whisper got quieter — Silero VAD pre-pass plus tighter no-speech thresholds cut the hallucinated phrases that used to appear during long silences.
- Auto-send destinations: route finished sessions to Notion, a webhook, or your MCP-connected LLM automatically — by folder or as a one-tap default in the kebab menu.
- Subtle start / pause / stop sound effects (toggle in Settings → Sounds).
- Security: MCP server no longer responds to cross-origin browser requests, and private data has been redacted from system logs.
- Notion form polish: labels align with their fields, the Test button has proper contrast, and the first-run sheet lays out cleanly.
Auto-updates handled by Sparkle. If you already have Daisy installed, open About → Check for Updates… or wait for the daily background check.