Skip to content

Name the silent microphone on the pill - #243

Merged
alexkroman merged 2 commits into
mainfrom
mic-silent-device-name
Oct 9, 2026
Merged

alexkroman merged 2 commits into
mainfrom
mic-silent-device-name

Conversation

@alexkroman

Copy link
Copy Markdown
Collaborator

Problem

When the mic delivers only silence, Blurt's liveness gate fails closed (correctly). The pill said only ERROR, and the reason, "The microphone didn't start.", was only in a hover tooltip. Nothing on screen pointed at the actual cause, so it looked like a Blurt bug.

Seen in the field: the default input was an Audio MIDI Setup aggregate of a Focusrite Scarlett and BlackHole. With the Scarlett unplugged, only BlackHole was left in the aggregate, delivering -758 dBFS on every channel. Seven presses in a row failed, all with the same unexplained error.

Change

The pill now names the device: AGGREGATE DEVICE ISN'T SENDING ANY AUDIO.

Engine

  • MicCaptureError.inputNeverDelivered(deviceName:). The name is looked up (AudioInputDevices.inputName(pinnedUID:)) only on the failure path, so a successful press pays nothing extra.
  • BlurtError drops the "Audio capture failed:" prefix for MicCaptureError, whose messages are already user-facing sentences. Other capture errors keep it.
  • New OverlayUIState.inputSilent(deviceName:), which owns the wording:
    • pillNotice (subject + predicate) for the pill
    • the sentence form for VoiceOver and hover
    • a 3 s dwell
  • Every other failure still maps to .error and keeps the compact ERROR pill.

Overlay (Mac shell)

  • For this notice only, the panel widens to fit the text. It stays centred, is capped at 340 pt, and is clamped onto the screen. Afterwards it returns to the saved drag position, which is still stored in standard-width coordinates.
  • Long device names truncate in the middle. "isn't sending any audio" never truncates, and the text never shrinks.
  • The swap to the wide pill happens under a panel fade, not the usual content cross-fade. Two reasons:
    • The window resizes a frame before SwiftUI redraws.
    • withTransaction doesn't reach this view, because the @Observable bridge is applied in a later update. A flag on the bridge carries "don't cross-fade this change" instead.
  • NSHostingView.sizingOptions = [] so the controller alone sizes the panel.

Verification

  • swift test: 774 tests pass, including new ones for the message, pillNotice/pillText, the sentence form, and "only this notice writes on the pill".
  • swift-format lint --strict, swiftlint --strict and check-invariants.sh pass. The Debug app and dev-build.sh both build.
  • On hardware: Blurt Dev pinned to a silent BlackHole input. Burst screenshots show "Connecting" changing straight to the centred wide pill with no glitch frames. A throwaway build with the cap cut to 200 pt confirmed truncation: "…ISN'T SENDING ANY AUDIO" stays whole at full size.
  • swift6-concurrency-reviewer: no issues; its suggested comment on payload-blind error equality is added.
  • macos-hig-reviewer: its truncation, shrink and edge-clamp findings are fixed.
  • Not changed: the reviewer suggested 10–11 pt sentence case for this notice instead of the pill's 9 pt status-line capitals. That's a design call I left out of this PR.

Not in this PR

The log line still says "input liveness unconfirmed … — proceeding" even though the press fails there. It's a one-word follow-up.

🤖 Generated with Claude Code

When the liveness gate fails closed, the pill said only "Error", with
"The microphone didn't start." on hover — which sent users hunting for a
Blurt bug when the cause was their audio setup (an aggregate whose USB
interface was unplugged, leaving a silent BlackHole as the default input).

The failure now carries the device name, and the pill says it:
"AGGREGATE DEVICE ISN'T SENDING ANY AUDIO".

- MicCaptureError.inputNeverDelivered(deviceName:), looked up only on the
  failure path; BlurtError drops the "Audio capture failed:" prefix for
  MicCaptureError, whose messages already read as sentences.
- OverlayUIState.inputSilent owns the wording: pillNotice (subject +
  predicate) and the sentence form for VoiceOver/hover; 3 s dwell.
- The overlay widens the panel for this notice only (centred, capped at
  340 pt, clamped on screen), swaps under a panel fade because the window
  resizes a frame before SwiftUI redraws, and returns to the saved
  placement after. Long names truncate in the middle; the predicate never
  truncates and nothing shrinks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alexkroman
alexkroman enabled auto-merge (squash) October 9, 2026 20:28
The new OverlayUIState.inputSilent case left three iPhone switches
non-exhaustive. The iPhone has no widened pill, so it reads as an error
whose message is the sentence naming the device. Also wraps an
over-long .animation line for swift-format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alexkroman
alexkroman merged commit 976cde3 into main Oct 9, 2026
12 checks passed
@alexkroman
alexkroman deleted the mic-silent-device-name branch October 9, 2026 21:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants