uttrflow / uttrflow/uttrflow-swift
[P2] Restore the dictation shortcut when Settings closes or leaves the shortcut recorder
- Dominant language
- Swift
- Stars
- 4
- Forks
- 17
- Avg merge
- 3h 32m
- Merged PRs (30d)
- 277
Description
Starting shortcut recording stops the live dictation shortcut controller. Closing Settings bypasses the callback that restarts it, and switching away from General leaves the recording session active after its keyboard source disappears. Ordinary navigation can therefore leave the dictation shortcut stopped even though no shortcut change was saved.
On main `533e6cbef5046f8bd681246d99ebf9ef9553fa40`:
- `SettingsViewModel.beginRecordingShortcut` calls `onShortcutRecording(true)`. In `AppDelegate.shortcutRecordingChanged`, this stops `controller`; the `false` branch calls `startWatchingForTheShortcut()`.
- [SettingsWindowController.windowWillClose](https://github.com/uttrflow/uttrflow-swift/blob/533e6cbef5046f8bd681246d99ebf9ef9553fa40/Sources/Uttrflow/Settings/SettingsWindowController.swift#L74-L79) calls `model.session.cancelRecordingShortcut()` directly. It never calls the view model's cancellation method, which emits `onShortcutRecording(false)`.
- `SettingsRootView.tab` assigns `model.session.tab` directly. The disappearing `SettingsShortcutField` only calls `stopListening()`, which stops its own keyboard source, without ending the session or notifying the app.
User paths to cover: General → Change on a shortcut → close Settings without entering a key; and General → Change → select another settings tab. Once the start callback has stopped the dictation controller, neither navigation path sends the matching resume callback. Explicit Cancel is a working control path. This report is about dictation shortcut restoration, not every global shortcut or a microphone left recording.
Validation used the unmodified production `SettingsViewModel` in an isolated Swift target, fake settings/personalisation stores, and a callback recorder. After beginning recording, executing the exact session operation from `windowWillClose` leaves `isRecording == false` but callbacks `[true]`, rather than `[true, false]`. Executing the tab button's assignment leaves `isRecording == true` and callbacks `[true]`. Calling the real view model's Cancel method produces `[true, false]` and ends recording. Two regression probes fail (three assertions); the Cancel control passes. The view/controller/AppDelegate call paths were inspected in source; no live window interaction or native hotkey test was performed.
Route window-close and tab-exit cleanup through a single view-model operation that both ends recording and restores the live controller. Test close, tab change, explicit Cancel, accepted/rejected recordings, and repeated cleanup; account for queued keyboard callbacks so a stale event cannot restore or modify a newer recorder. Preserve the previous binding whenever no replacement was accepted.
Checked all-state issues and open PRs for this lifecycle failure. Existing keyboard teardown/crash reports concern different causes.
Contributor guide
Research direction
Start with SettingsWindowController.windowWillClose, SettingsViewModel's recording operations, SettingsRootView.tab, and AppDelegate.shortcutRecordingChanged to trace the existing cleanup paths. Run the two regression probes described in the issue and cover close, tab change, Cancel, accepted and rejected recordings, and repeated cleanup. Done means every exit path restores dictation, stale keyboard callbacks cannot affect a newer recording, and an unaccepted change preserves the previous binding.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- swift
- Domain
- desktop, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 70/100