Fix Getting Started download: transparent progress, non-modal window, base model guarantee#28
Fix Getting Started download: transparent progress, non-modal window, base model guarantee#28SoEasy wants to merge 4 commits into
Conversation
… base model guarantee
| case .failed: | ||
| return "Download failed" | ||
| } | ||
| } |
There was a problem hiding this comment.
Parallel enums duplicate display text creating maintenance risk
Low Severity
DownloadFilePhase and DownloadStage share six cases with identical names and identical displayText values (e.g. preparing, creatingModelFolder, checkingExistingModels, checkingStorageLimit, checkingFreeSpace, fetchingFileList). DownloadFilePhase already has a downloadStage computed property mapping phases to stages, but the display text is duplicated independently. Changing text in one enum without updating the other will produce inconsistent UI strings.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 0cb6f80. Configure here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4e3866d. Configure here.
| } | ||
|
|
||
| // Check available disk space | ||
| let availableSpace = try await getAvailableStorageSpace() |
There was a problem hiding this comment.
Nil model directory silently continues without error
Low Severity
When WhisperKitStorage.modelDirectory(for: model) returns nil, the empty else branch silently continues the download flow. Subsequent preparation steps (storage limit checks, free space checks) execute and show progress to the user, only to eventually fail inside WhisperKitCoreMLFiles.install with an applicationSupportDirectoryNotFound error. Failing early with a clear error at the point the directory is known to be unavailable would avoid confusing the user with misleading progress before the inevitable failure.
Reviewed by Cursor Bugbot for commit 4e3866d. Configure here.


Model download progress and Getting Started fix
Problem
When a user launched AudioWhisper for the first time and clicked Get Started, they experienced the following:
From the user's point of view the app looked broken: no feedback on what was happening, no way to know whether to keep waiting or restart.
There were four root causes, any one of which was enough to leave the user with a broken experience.
1. WhisperKit did not download the files required for transcription to work
WhisperKit(config)downloads the CoreML model bundles (encoder, decoder, mel spectrogram) but does not download the tokenizer and vocabulary files that the model needs at inference time:These files must exist both at the model root and inside a
models/subdirectory. Without them, WhisperKit loads silently but fails to transcribe — the model never appeared as "downloaded" in Settings and audio transcription produced no output.The only workaround was a manual shell script that fetched these files from the OpenAI HuggingFace repo and copied them into the right locations:
Only after running this script would a model appear as ready in Settings and transcription start working.
2. The download gave no feedback
Previously the entire model installation was delegated to a single
WhisperKit(config)call, which handled everything internally. This meant:3. The download never actually started
The Getting Started window was presented via
NSApplication.runModal(for:), which runs the macOS run loop inNSModalPanelRunLoopMode. Swift Concurrency's@MainActorexecutor is backed byDispatchQueue.main, which only fires inNSDefaultRunLoopModeandNSEventTrackingRunLoopMode— not inNSModalPanelRunLoopMode.As a result, any
Task { }created inside the welcome view inherited the@MainActorcontext and was enqueued on the main actor, but the main actor never got to process it. The task body never executed, andawait MainActor.run { }calls deeper in the download path were equally blocked. The spinner showed becauseisDownloadingModel = truewas set synchronously in the button action, but nothing progressed beyond that.4. A reinstalled app could download the wrong model
On macOS,
UserDefaultsare stored in~/Library/Preferences/and are not removed when the user deletes the app. If a user had previously changed the transcription model to, say,largeTurboin Settings, uninstalled, and then reinstalled, the Getting Started screen would read that saved preference via@AppStorageand attempt to download the large model instead of the lightweight base model — with no indication to the user that this was happening.Solution
Replaced the modal window with an async non-modal window
WelcomeWindow.showWelcomeDialog()was changed from a blockingrunModalcall to anasyncfunction that suspends viawithCheckedContinuationand shows the window normally. The main thread is no longer stuck in a private run loop mode — it processes events normally, so Swift Concurrency tasks run as expected.WelcomeViewnow receives anonComplete: (Bool) -> Voidcallback. When the user finishes setup,completeWelcome()invokes the callback, which resumes the continuation and lets the caller (showWelcomeAndSettings) proceed to open the Dashboard.The downstream callers in
AppDelegatewere updated to callawait showWelcomeAndSettings()inside aTask { @MainActor in }.Replaced the opaque WhisperKit download with a transparent file-by-file download that includes the missing tokenizer files
Instead of relying on
WhisperKit(config)to fetch model files, the installation is now done directly:WhisperKitCoreMLFiles— fetches the file list from the HuggingFace API (argmaxinc/whisperkit-coreml) and downloads each CoreML bundle individually, reporting(completedFiles, totalFiles, currentFileName)progress.WhisperKitSupplementalFiles— downloads the seven tokenizer and vocabulary files from the corresponding OpenAI HuggingFace repo (openai/whisper-{model}), places them at the model root and inside themodels/subdirectory, and verifies all files are present before marking the model as ready. This replaces the manual shell-script workaround that was the only way to get transcription working before.ModelManagernow tracks this granular state indownloadFileProgress: [WhisperModel: DownloadFileProgress]and exposes it to the UI.DownloadStagewas extended with preparation sub-stages (creatingModelFolder,checkingExistingModels,checkingStorageLimit,checkingFreeSpace,fetchingFileList) so the UI can show meaningful text at every step.The Welcome view and the Dashboard's local Whisper panel were updated to display the file count progress (
3 / 47 files downloaded) and the name of the file currently being downloaded.Hardcoded the base model for the Getting Started flow
WelcomeViewno longer reads the model to download from@AppStorage. Instead it declares a constantwelcomeModel: WhisperModel = .baseand uses it exclusively throughout the onboarding flow — for the download itself, for progress lookups, and for error display. The@AppStoragebinding forselectedWhisperModelwas removed from this view entirely.completeWelcome()already wroteAppDefaults.defaultWhisperModel(.base) back toUserDefaultson completion, so after a successful setup the preference is always reset to a known-good value regardless of what was stored before.Test coverage
A new test file
Tests/ModelDownloadProgressTests.swift(21 tests, 0 failures) covers all pure value types introduced in this PR.What is covered
DownloadFileProgressTestsdisplayTextwithout total, with total, clamping when completed > total;detailTextwith error, with filename, without filename, error priority over filenameDownloadFilePhaseTestsDownloadStage; download/terminal phases map to correct stagesDownloadStageNewCasesTestsisActive = trueand non-empty display text;readyandfailedare not activeModelErrorDescriptionTests.downloadFileFaileddescription contains file name, repo, and reason; existing error cases remain non-emptyWhisperModelOpenAIRepoTestshuggingface.co; URL contains repo nameWhat is not covered and why
WhisperKitCoreMLFiles.installandWhisperKitSupplementalFiles.installare not unit-tested. Both methods callURLSession.shareddirectly with no way to inject a mock. Writing meaningful tests would require either a live network call (flaky, slow, environment-dependent) or adding aURLSessionparameter to the production code (out of scope for this change). The file-fetching and error-handling logic of these two services can be covered in a follow-up that adds theURLSessioninjection point.Files changed
WelcomeWindow.swiftrunModalwithasync/withCheckedContinuation, non-modal windowWelcomeView.swiftonCompletecallback, file-level progress display, removedstopModal, hardcoded base model for onboardingAppDelegate+Lifecycle.swiftshowWelcomeAndSettings()is nowasyncAppDelegate+Menu.swiftshowHelp()callsshowWelcomeAndSettings()viaTaskModelManager.swiftDownloadFileProgress,DownloadFilePhase, extendedDownloadStage, direct download flow replacingWhisperKit(config)WhisperKitCoreMLFiles.swiftWhisperKitSupplementalFiles.swiftWhisperKitStorage.swiftWhisperModel+WhisperKit.swiftopenAIWhisperRepoName/openAIWhisperRepoURLfor supplemental file downloadsModelEntry.swiftDashboardProviders+LocalWhisper.swiftNote
Medium Risk
Changes the model download/installation pipeline and file verification logic, plus introduces new network download code paths; failures here can block offline transcription, though scope is localized and includes new unit tests for the progress/error types.
Overview
Fixes first-run onboarding getting stuck by converting the Welcome dialog from modal
runModalto an async, non-modal window (WelcomeWindow.showWelcomeDialog()+AppDelegate.showWelcomeAndSettings()), with a single-instance guard and a completion callback fromWelcomeView.Reworks local Whisper model installation to be transparent and reliable:
ModelManager.downloadModelnow downloads CoreML bundles and required tokenizer/vocab files explicitly via newWhisperKitCoreMLFilesandWhisperKitSupplementalFiles, adds granular preparation stages + per-file progress (DownloadFileProgress/DownloadFilePhase), improves error reporting (ModelError.downloadFileFailed), and tightens “model downloaded” checks to verify supplemental files.Updates UI to display file counts/current filename and retry on failure (Welcome screen, dashboard provider list, recording/status messaging), hard-codes onboarding to always download the base model, adds tests for the new progress/value types, and ignores
.ideain.gitignore.Reviewed by Cursor Bugbot for commit 4e3866d. Bugbot is set up for automated code reviews on this repo. Configure here.