Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
3bf61bd
Harden privacy and updater paths
schallau Jul 5, 2026
c662239
Fix SwiftLint Makefile detection
schallau Jul 5, 2026
5c63a0f
Fix updater defaults and feedback
schallau Jul 5, 2026
12e52f2
Improve model download and selection UX
schallau Jul 5, 2026
dcc14d3
Polish history and file transcription flow
schallau Jul 5, 2026
9ebdd5d
Capture audio from the first buffer to stop dropping opening words
schallau Jul 5, 2026
52da9bf
Add model-load watchdog so a hung load can't wedge the recorder
schallau Jul 5, 2026
2836b1d
Paste as soon as the target app has focus instead of a fixed 500ms
schallau Jul 5, 2026
08925ec
Fix mid-recording device loss, denied-mic dead-end, and launch race
schallau Jul 5, 2026
056d60b
Separate download status notes from errors and clarify no-model error
schallau Jul 5, 2026
f151443
Validate Parakeet caches properly and stop silent loads-as-downloads
schallau Jul 5, 2026
cd63f52
Skip launch-time history re-normalization and sweep orphaned audio
schallau Jul 5, 2026
a077bf1
Install keystroke monitors only while they can do anything
schallau Jul 5, 2026
bea407f
Delete dead screens, models, and the unused KeyboardShortcuts hookup
schallau Jul 5, 2026
4e368e3
Show load stage and elapsed time while the model warms up
schallau Jul 5, 2026
8f0fc60
Let onboarding finish without Accessibility
schallau Jul 5, 2026
932e1e0
Draw the real audio waveform in history playback
schallau Jul 5, 2026
8a5619a
Guard download cancel races and pre-flight disk space
schallau Jul 5, 2026
2990c21
Coalesce live level/waveform publishes to 30 Hz
schallau Jul 5, 2026
f25ad31
Allow cancelling a stalled update download
schallau Jul 5, 2026
1cf9054
Remove the KeyboardShortcuts package dependency
schallau Jul 5, 2026
ea48c67
Rewrite UI tests against the current interface
schallau Jul 5, 2026
3e7bd11
Show the configured hotkey in Dashboard's empty state
schallau Jul 5, 2026
fd88eb8
Fix watchdog killing legitimate first loads; make stale loads throw
schallau Jul 5, 2026
7b5212b
Don't let cancelled-download cleanup delete a retry's files
schallau Jul 5, 2026
0680a52
Wait for the mic-permission answer before recording starts
schallau Jul 5, 2026
ff182ae
Explain the one-time model optimization during first load
schallau Jul 5, 2026
25c501c
Add Option+Space as a hotkey choice
schallau Jul 5, 2026
337e815
Add a setting to hide the idle pill; recover the tap after grant
schallau Jul 5, 2026
6526b97
Use per-download generation tokens to end the cancel/retry delete race
schallau Jul 5, 2026
0f025b4
Enter the recording HUD only when capture actually begins
schallau Jul 5, 2026
b28ffbe
Don't drop a quick Option+Space release
schallau Jul 5, 2026
14e0a12
Strip trailing whitespace in DeviceRow
schallau Jul 5, 2026
483eaa5
Close the remaining download-generation races surfaced in review
schallau Jul 5, 2026
01eb1b2
Gate device-switch resume and model-switch on success
schallau Jul 5, 2026
9534ce0
Dedupe the Software Update window
schallau Jul 5, 2026
ed26406
Serialize per-variant downloads so cancel/retry can't overlap writers
schallau Jul 5, 2026
2860246
Report recording started only when capture is genuinely underway
schallau Jul 5, 2026
0bc6e3e
Only revert model selection if the failed load is still selected
schallau Jul 5, 2026
52445cd
Chain a retry behind the post-cancel cleanup delete, not just the dow…
schallau Jul 5, 2026
abf65a1
Don't spin an inputless session on mid-recording device loss
schallau Jul 5, 2026
e1a3f07
Add unit coverage for behavior changed this session
schallau Jul 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -106,20 +106,32 @@ test-ui:
# Run SwiftLint
lint:
@echo "Running SwiftLint..."
@which swiftlint > /dev/null && swiftlint || echo "⚠️ SwiftLint not installed"
@if ! command -v swiftlint > /dev/null; then \
echo "⚠️ SwiftLint not installed. Install with: brew install swiftlint"; \
exit 1; \
fi
swiftlint

# Reproduce the GitHub Actions CI checks locally (build + unit tests + lint)
ci:
@echo "Running CI checks locally..."
xcodebuild -scheme speaktype -configuration Debug build CODE_SIGNING_ALLOWED=NO
xcodebuild test -scheme speaktype -destination 'platform=macOS' -only-testing:speaktypeTests CODE_SIGNING_ALLOWED=NO
@echo "--- SwiftLint (advisory) ---"
@which swiftlint > /dev/null && swiftlint || echo "⚠️ SwiftLint not installed"
@if command -v swiftlint > /dev/null; then \
swiftlint || echo "⚠️ SwiftLint reported violations"; \
else \
echo "⚠️ SwiftLint not installed. Install with: brew install swiftlint"; \
fi

# Auto-fix SwiftLint issues
format:
@echo "Formatting code with SwiftLint..."
@which swiftlint > /dev/null && swiftlint --fix || echo "⚠️ SwiftLint not installed"
@if ! command -v swiftlint > /dev/null; then \
echo "⚠️ SwiftLint not installed. Install with: brew install swiftlint"; \
exit 1; \
fi
swiftlint --fix

# Clean build artifacts
clean:
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,10 @@ make dmg # Create DMG installer

### Current Issues

⚠️ When loading a model for the first time / switching to another model, there is a startup delay of 30-60 seconds.

So the first transcription will appear ultra slow, but it will go back to instantaneous dictation right after it's warmed up.
⚠️ The first load of a freshly downloaded model runs a one-time CoreML/Neural Engine
optimization pass — around 30-60 seconds for small models and up to a few minutes for
large ones. The app shows a "First-time setup — optimizing model for your Mac" status
while this runs; macOS caches the result, so every later load takes seconds.

### Project Structure

Expand All @@ -104,7 +105,6 @@ speaktype/

- **Swift 5.9+** / SwiftUI + AppKit
- **[WhisperKit](https://github.com/argmaxinc/WhisperKit)** - Local Whisper inference
- **[KeyboardShortcuts](https://github.com/sindresorhus/KeyboardShortcuts)** - Global hotkeys
- **AVFoundation** - Audio capture

---
Expand Down
3 changes: 1 addition & 2 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,7 @@ Error: HTTP status code: 401. Unable to authenticate.
```bash
xcrun notarytool store-credentials "AC_PASSWORD" \
--apple-id "mail2048labs@gmail.com" \
--team-id "PCV4UMSRZX" \
--password "NEW_PASSWORD_HERE"
--team-id "PCV4UMSRZX"
```

### Notarization Rejected
Expand Down
11 changes: 5 additions & 6 deletions scripts/lib/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ resolve_version() {
fi
}

# Ensure notarization credentials exist in the keychain, prompting once for an
# app-specific password if the profile is missing.
# Ensure notarization credentials exist in the keychain. When the profile is
# missing, notarytool prompts for the app-specific password itself so the secret
# never appears in this script's process arguments.
ensure_notary_credentials() {
if xcrun notarytool history --keychain-profile "$NOTARY_PROFILE" &>/dev/null; then
return 0
Expand All @@ -81,14 +82,12 @@ ensure_notary_credentials() {
echo " 2. Sign in with: $APPLE_ID"
echo " 3. Security → App-Specific Passwords → Generate"
echo ""
read -rp "Enter app-specific password: " -s APP_PASSWORD
echo "notarytool will prompt for the app-specific password securely."
echo ""
[ -z "$APP_PASSWORD" ] && { echo "❌ Password required"; exit 1; }

xcrun notarytool store-credentials "$NOTARY_PROFILE" \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$APP_PASSWORD"
--team-id "$APPLE_TEAM_ID"
echo ""
echo "✅ Credentials stored. Continuing..."
echo ""
Expand Down
17 changes: 0 additions & 17 deletions speaktype.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

/* Begin PBXBuildFile section */
1C547F2B2F0E7DC0008120A6 /* WhisperKit in Frameworks */ = {isa = PBXBuildFile; productRef = 1C547F2A2F0E7DC0008120A6 /* WhisperKit */; };
1CE4CB3F2F0EF6AF00C01C62 /* KeyboardShortcuts in Frameworks */ = {isa = PBXBuildFile; productRef = 1CE4CB3E2F0EF6AF00C01C62 /* KeyboardShortcuts */; };
1CE4CB412F0EF86B00C01C62 /* whisperkit-cli in Frameworks */ = {isa = PBXBuildFile; productRef = 1CE4CB402F0EF86B00C01C62 /* whisperkit-cli */; };
FA0000000000000000000003 /* FluidAudio in Frameworks */ = {isa = PBXBuildFile; productRef = FA0000000000000000000002 /* FluidAudio */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -73,7 +72,6 @@
buildActionMask = 2147483647;
files = (
1C547F2B2F0E7DC0008120A6 /* WhisperKit in Frameworks */,
1CE4CB3F2F0EF6AF00C01C62 /* KeyboardShortcuts in Frameworks */,
1CE4CB412F0EF86B00C01C62 /* whisperkit-cli in Frameworks */,
FA0000000000000000000003 /* FluidAudio in Frameworks */,
);
Expand Down Expand Up @@ -145,7 +143,6 @@
name = speaktype;
packageProductDependencies = (
1C547F2A2F0E7DC0008120A6 /* WhisperKit */,
1CE4CB3E2F0EF6AF00C01C62 /* KeyboardShortcuts */,
1CE4CB402F0EF86B00C01C62 /* whisperkit-cli */,
FA0000000000000000000002 /* FluidAudio */,
);
Expand Down Expand Up @@ -233,7 +230,6 @@
minimizedProjectReferenceProxies = 1;
packageReferences = (
1C547F222F0E7D07008120A6 /* XCRemoteSwiftPackageReference "WhisperKit" */,
1CE4CB3D2F0EF6AF00C01C62 /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */,
FA0000000000000000000001 /* XCRemoteSwiftPackageReference "FluidAudio" */,
);
preferredProjectObjectVersion = 77;
Expand Down Expand Up @@ -624,14 +620,6 @@
version = 0.9.4;
};
};
1CE4CB3D2F0EF6AF00C01C62 /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/sindresorhus/KeyboardShortcuts";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.4.0;
};
};
FA0000000000000000000001 /* XCRemoteSwiftPackageReference "FluidAudio" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/FluidInference/FluidAudio.git";
Expand All @@ -648,11 +636,6 @@
package = 1C547F222F0E7D07008120A6 /* XCRemoteSwiftPackageReference "WhisperKit" */;
productName = WhisperKit;
};
1CE4CB3E2F0EF6AF00C01C62 /* KeyboardShortcuts */ = {
isa = XCSwiftPackageProductDependency;
package = 1CE4CB3D2F0EF6AF00C01C62 /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */;
productName = KeyboardShortcuts;
};
1CE4CB402F0EF86B00C01C62 /* whisperkit-cli */ = {
isa = XCSwiftPackageProductDependency;
package = 1C547F222F0E7D07008120A6 /* XCRemoteSwiftPackageReference "WhisperKit" */;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading