Skip to content

History UI, Whisper Models fix - #5

Closed
iamtheorm wants to merge 1 commit into
mainfrom
ormversion
Closed

History UI, Whisper Models fix#5
iamtheorm wants to merge 1 commit into
mainfrom
ormversion

Conversation

@iamtheorm

Copy link
Copy Markdown
Collaborator

Summary

This PR significantly improves the History user interface, integrates higher-quality Whisper AI models, adds robust automated testing, and performs a major project cleanup to remove unused legacy code.


Key Changes

1. History Tab Redesign

  • Expandable List Layout
    Replaced the buggy SplitView with a clean, single-column expandable list.

  • Inline Actions
    Added a dedicated Copy Transcript button inside each expanded history item.

  • Improved UX
    Fixed layout margins for better readability and added Swipe to Delete support.

  • Bug Fix
    Resolved an issue where the history list disappeared when the window was minimized.


2. Advanced AI Models

  • New Models Added

    • Whisper Large v3 Turbo — best balance of speed and accuracy
    • Whisper Medium
  • Performance Optimization

    • Updated Base and Small models to use English-only (.en) variants for better macOS performance.
  • Fix

    • Corrected model ID naming conventions to prevent model download failures.

3. Transcription Features

  • Drag & Drop Support
    Users can now drag audio files directly into the Transcribe Audio view.

  • File Importer

    • Added a manual file picker button for selecting audio files.

4. Comprehensive Testing

  • Unit Tests

    • ClipboardService
    • HistoryService
    • ModelDownloadService
  • UI Tests

    • Sidebar navigation
    • Settings navigation
    • Fixed macOS simulator timeout instability

5. Project Cleanup

  • Removed

    • Unused ContentView.swift
    • Default Xcode test templates
    • Verbose and obsolete debug comments
  • Security Verification

    • Confirmed system-level NSSecureCoding logs are benign and unrelated to application security.

✅ Verification

  • All unit tests passed
  • UI tests passed (Sidebar & Settings navigation)
  • Manual verification of model downloads (Turbo / Medium)
  • Manual verification of drag-and-drop transcription

@iamtheorm iamtheorm changed the title Enhanced History UI, Advanced Whisper Models, and Project Cleanup History UI, Whisper Models fix Jan 11, 2026
@iamtheorm iamtheorm closed this Jan 11, 2026
aschwarzkopf added a commit to aschwarzkopf/speaktype that referenced this pull request Apr 24, 2026
Full code audit surfaced 15 bugs across 4 severity tiers; this commit
addresses 14 of them (karansinghgit#15 deferred — Polar.sh handles server-side). It
also introduces MiniRecorderViewModel, which fixes a subscription-timing
race that caused "box appears, disappears, no audio" when pressing the
hotkey.

All 58 unit tests pass. 2 pre-existing UI test failures are unrelated
(sidebar link lookups).

CRITICAL

karansinghgit#1 AudioRecordingService writer race — replaced `isStopping: Bool` with a
   RecordingStatus enum gated on audioQueue (RosyWriter pattern). All
   writer-state mutation now happens on audioQueue; concurrent/double
   stopRecording calls are safe.
karansinghgit#2 MiniRecorderView NSEvent monitor leak + duplicate Escape handling —
   deleted the global/local NSEvent monitors; introduced
   KeyableMiniRecorderPanel (canBecomeKey=true) so the existing
   KeyEventHandlerView becomes the sole Escape path.
karansinghgit#3 MiniRecorder state hygiene — fixed via the VM refactor below.

HIGH

karansinghgit#4 ModelDownloadService retry bounds — added `maxDownloadAttempts = 2`,
   `defer { activeTasks[variant] = nil }` for exactly-once handle cleanup.
karansinghgit#5 AIModel.recommendedModel force unwrap — replaced with precondition +
   guarded fallback to smallest model.
karansinghgit#6 TranscribeAudioView security-scoped resource — applied Apple's `defer`
   pattern, UUID-prefixed temp names, dropped unsound fallback to scoped
   URL after access was released.
karansinghgit#7 AudioPlayerService silent load failure — loadAudio now async throws;
   added @published var lastError for SwiftUI .alert(item:) observation;
   made the service @mainactor; updated 2 callers.
karansinghgit#8 LicenseManager race — added @published isValidatingLicense flag;
   network/transport failures no longer revoke cached license (only
   explicit server-confirmed invalid responses do), per RevenueCat /
   StoreKit 2 pattern.

MEDIUM

karansinghgit#9  WhisperService loadModel cancellation — pre-guard and post-guard
    Task.checkCancellation() per WWDC21 guidance; `defer` guarantees
    isLoading resets; late-cancelled pipe is discarded.
karansinghgit#10 ModelDownloadService deleteModel — dropped /tmp and ~/.cache paths;
    replaced substring `contains` match with exact last-path-component
    equality; extracted static `shouldDelete(fileName:patterns:)` for
    testability.
karansinghgit#11 UpdateService stale state — on network error, clear availableUpdate
    to avoid showing outdated update banner; added clearAvailableUpdate()
    helper.
karansinghgit#12 MiniRecorderView.barHeight clamp — defensive min/max on audioLevel
    before sqrt() to guard against upstream bugs producing NaN.

LOW

karansinghgit#13 HistoryService deleteItem — filter out-of-bounds offsets before
    `remove(atOffsets:)` to tolerate stale IndexSet during rapid deletes.
karansinghgit#14 MiniRecorderView debugLog — moved off world-readable /tmp to
    ~/Library/Application Support/SpeakType/Logs; gated behind #if DEBUG
    so release builds use print (unified logging) instead.

VM REFACTOR (Option D from research)

Fixes the "no audio on Fn press" bug introduced by karansinghgit#3's initial attempt
to use `.id(sessionID)` for SwiftUI @State reset. The .id() rebuild
forced SwiftUI to teardown/rebuild the view, but notifications posted
synchronously after the swap hit an empty subscriber list — recording
was never triggered.

- New MiniRecorderViewModel (@mainactor, ObservableObject) owns all
  session-lifecycle state: isListening, isProcessing, isWarmingUp,
  statusMessage, cancelCommit, sessionID, pendingAction
- Controller drives the VM directly via startSession/requestStop/
  requestCancel — no more NotificationCenter round-trip, no more .id()
  churn
- View observes via @ObservedObject; three .onReceive blocks collapsed
  into one .onChange(of: viewModel.pendingAction) draining consumeAction()
- SwiftUI guarantees @published subscribers are attached before any
  mutation is observed, eliminating the timing race

Test additions:
- 3 audio concurrency tests, 3 panel, 5 controller, 4 AIModel, 3 audio
  player, 2 license, 5 exact-match deletion + 1 retry bound, 1 Whisper
  cancellation, 2 update service, 2 history offset, 12 VM contract tests

Co-Authored-By: Claude Opus 4.7 (1M context) <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.

1 participant