Skip to content

feat: Thread-safety, hover-expand pill UI, and centralized date formatting#3

Merged
r3dbars merged 1 commit into
mainfrom
feat/thread-safety-and-hover-pill
Jan 1, 2026
Merged

feat: Thread-safety, hover-expand pill UI, and centralized date formatting#3
r3dbars merged 1 commit into
mainfrom
feat/thread-safety-and-hover-pill

Conversation

@r3dbars

@r3dbars r3dbars commented Jan 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • Thread-safety improvements: Add NSLock wrappers to prevent race conditions during device changes while recording
  • New hover-expand idle pill UX: Redesigned minimal pill that expands on hover to reveal Record/Files buttons
  • Centralized date formatting: New DateFormattingHelper with cached formatters, eliminating duplicate initialization
  • Code cleanup: Remove ~180 lines of deprecated code and unused methods

Changes

Thread Safety

  • Add NSLock wrapper for lastBufferTime in Audio.swift and SystemAudioCapture.swift
  • Fix race condition in audio file closure by using queue.sync before closing files

New Hover-Expand Idle Pill

State Size Content
Collapsed 40×20px Minimal 3-bar waveform icon with subtle breathing
Expanded 120×28px Record + Files buttons with hover highlights
  • Respects accessibilityReduceMotion preference
  • New MinimalWaveformIcon and ExpandedPillButton components

DateFormattingHelper

Cached formatters for consistent date handling:

  • formatFilenamePrecise() — with milliseconds for audio files
  • formatFilename() — for transcript filenames
  • formatDisplay() — user-facing format
  • formatDuration() — TimeInterval → "MM:SS"

Cleanup

  • Remove requestPermissions() from AppDelegate (now in onboarding)
  • Remove unused CoreAudioUtils methods
  • Remove deprecated FloatingPanelView code

Test plan

  • Build succeeds with no errors
  • App launches and displays floating pill
  • 5-minute recording test completed successfully
  • Transcript saved with correct formatting
  • Verify hover-expand animation on idle pill
  • Verify Record button starts recording
  • Verify Files button opens transcripts folder

🤖 Generated with Claude Code

…ormatting

## Thread Safety
- Add NSLock wrapper for `lastBufferTime` in Audio.swift and SystemAudioCapture.swift
- Fix race condition in audio file closure by using queue.sync before closing files
- Prevents potential crashes during device changes while recording

## New Hover-Expand Idle Pill UX
- Redesign idle pill from tap-to-record to hover-to-expand pattern
- Collapsed state: 40x20px with minimal 3-bar waveform icon
- Expanded state: 120x28px revealing Record and Files buttons side-by-side
- Add subtle breathing animation to MinimalWaveformIcon
- Add ExpandedPillButton component with hover highlights
- Respects accessibilityReduceMotion preference

## Centralized Date Formatting
- Add DateFormattingHelper.swift with cached DateFormatter instances
- Migrate Audio.swift and TranscriptSaver.swift to use centralized formatters
- Eliminates duplicate DateFormatter initialization across codebase
- Methods: formatFilenamePrecise(), formatFilename(), formatDisplay(), formatDuration()

## Cleanup
- Remove unused requestPermissions() from AppDelegate (now in onboarding)
- Remove unused readProcessBundleID() and readProcessIsRunning() from CoreAudioUtils
- Remove unused audioFile property and setAudioFile() from SystemAudioCapture
- Remove ~180 lines of deprecated panel code from FloatingPanelView

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@r3dbars

r3dbars commented Jan 1, 2026

Copy link
Copy Markdown
Owner Author

Code Review

No issues found. Checked for bugs and CLAUDE.md compliance.

Review Summary

Reviewed by: 4 parallel agents (2 CLAUDE.md compliance, 2 bug detection)

Potential issues investigated and validated as false positives:

Issue Verdict Reason
DateFormatter thread-safety ✅ Not a bug DateFormatter is thread-safe on macOS 10.9+ (64-bit). This app targets macOS 13.0+ and formatters are never mutated after initialization.
Sync queue deadlock in stop() ✅ Not a bug No code path exists where stop() is called from within micAudioFileQueue or systemAudioFileQueue. All call sites are on main thread.
CLAUDE.md documentation missing ✅ Not a violation CLAUDE.md documents architectural flows, not every file. No explicit requirement to list all Core/ utilities.

Code quality observations:

  • Thread-safety improvements using NSLock follow correct patterns
  • DateFormattingHelper properly caches formatters for performance
  • Hover-expand pill UI respects accessibilityReduceMotion
  • Cleanup removes ~180 lines of deprecated code

🤖 Reviewed with Claude Code

@r3dbars r3dbars merged commit 4bd183f into main Jan 1, 2026
@r3dbars r3dbars deleted the feat/thread-safety-and-hover-pill branch January 1, 2026 03:05
r3dbars added a commit that referenced this pull request Mar 18, 2026
…ormatting (#3)

## Thread Safety
- Add NSLock wrapper for `lastBufferTime` in Audio.swift and SystemAudioCapture.swift
- Fix race condition in audio file closure by using queue.sync before closing files
- Prevents potential crashes during device changes while recording

## New Hover-Expand Idle Pill UX
- Redesign idle pill from tap-to-record to hover-to-expand pattern
- Collapsed state: 40x20px with minimal 3-bar waveform icon
- Expanded state: 120x28px revealing Record and Files buttons side-by-side
- Add subtle breathing animation to MinimalWaveformIcon
- Add ExpandedPillButton component with hover highlights
- Respects accessibilityReduceMotion preference

## Centralized Date Formatting
- Add DateFormattingHelper.swift with cached DateFormatter instances
- Migrate Audio.swift and TranscriptSaver.swift to use centralized formatters
- Eliminates duplicate DateFormatter initialization across codebase
- Methods: formatFilenamePrecise(), formatFilename(), formatDisplay(), formatDuration()

## Cleanup
- Remove unused requestPermissions() from AppDelegate (now in onboarding)
- Remove unused readProcessBundleID() and readProcessIsRunning() from CoreAudioUtils
- Remove unused audioFile property and setAudioFile() from SystemAudioCapture
- Remove ~180 lines of deprecated panel code from FloatingPanelView

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: justinbetker <Justin.betker@jamf.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
r3dbars added a commit that referenced this pull request Apr 7, 2026
remove max_budget_usd cap from orchestrator
r3dbars added a commit that referenced this pull request Apr 7, 2026
remove max_budget_usd cap from orchestrator
r3dbars added a commit that referenced this pull request Apr 15, 2026
The dictation flow now waits on engine readiness instead of racing it.
Three stacked bugs fixed:

1. Retry budget too short for Bluetooth. Old logic retried 4× × 350ms
   (1.4s total). CoreAudio settle time is 100-500ms wired but 2-3+s for
   Bluetooth. Replaced with a 3.5s wait-for-ready loop that polls engine
   readiness flags every 150ms.

2. Two uncoordinated retry systems racing. ParakeetEngine.handleAudioConfigChange
   tore down the engine and waited 550ms before rebuilding, but
   DictationSessionController had no signal to know recovery was in flight
   and would call startRecording() on a stopped engine. ParakeetEngine now
   publishes isRecovering and inputFormatReady; the controller waits.

3. AirPods 24kHz HFP mode. AirPods on macOS run input in Hands-Free
   Profile (24kHz hw / 48kHz output bus) — never converges. Old code
   rejected the mismatch as transient; now we accept it and use the
   output bus rate (what installTap actually delivers) as nativeSampleRate
   so downstream resampling is correct.

Plus: bounded prewarm retry (10 attempts max, was infinite Task chain),
generation guard for stale recovery tasks, format-validation retry inside
attemptDeviceRecovery, copy update ("Couldn't reach {device}" instead of
"Microphone unavailable" with no diagnostic).

Verified end-to-end on AirPods Pro #3: cold start, mid-session swap,
4× rapid switch — all recover within 1 attempt. 7 new tests for the
extracted ParakeetRecoveryState policy struct.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
r3dbars added a commit that referenced this pull request Jul 7, 2026
…ansport

The negative-exemplar veto (#1487) was "sound but regime-limited" (eval in
#1493): after a correction it removed 46% of repeat wrong-matches in-room
(AMI) but only 12% cross-condition (VoxCeleb). Root cause: a single rejected
in-room sample and a later telephone/VoIP sample of the same rejected impostor
sit too far apart in embedding space for the raw cosine to clear the 0.80 veto
floor. The positive side already closed this gap with multi-exemplar
voiceprints (#1488); the negative side — one rejected embedding — had no analog.

Give the negative side the same multi-condition treatment (eval rec #3):
compare the candidate against each rejected sample AND against that sample
transported along the profile's own observed condition shifts (unit(exemplar) −
average). Channel/condition shifts are largely speaker-independent, so a
rejected sample shifted by a condition the profile has actually seen
approximates the same rejected voice returning in that other condition.

Owner-safe by construction:
- transport only along +(exemplar − average) — real, forward, profile-observed
  conditions; bidirectional/synthetic directions were measured to leak
  owner-collateral and are not used.
- a profile with no positive exemplars derives nothing → reduces exactly to the
  raw maxNegativeSimilarity → single-condition profiles unchanged.
- the 0.80 floor and the ≥positiveSimilarity owner gate are untouched; transport
  only widens which impostor returns are caught, never lowers the floor.

Real qmatrix eval (SpeakerExemplarDeltaEvalTests): AMI cross-condition
vetoed-among-re-match 0.350 → 0.374 (+7.0% rel) with owner-collateral flat
(2.17% → 2.32%, +9 of 5773 checks). VoxCeleb (single-utterance corpus, no
condition structure) byte-identical, incl. owner-collateral — no regression.
Pushing VoxCeleb's 12% further was investigated and rejected: every mechanism
that moves it trades owner-collateral ~1:1, violating the #1493 guardrail.

See docs/speaker-eval-negative-veto-cross-condition-2026-07.md. Refs #1493.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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