uttrflow / uttrflow/uttrflow-swift

Stopping a double-tap dictation from the menu bar or at the 4-minute cap leaves the dictation shortcut dead until relaunch

Open
#602 0 comments 0 reactions 0 assignees View on GitHub
area:dictation bug P1
Dominant language
Swift
Stars
4
Forks
17
Avg merge
3h 32m
Merged PRs (30d)
277

Description

## What happens

`DictationController` remembers a double-tap (hands-free) dictation in `isHandsFree`, but only two paths ever clear it: a second double tap (`stopHandsFree`, `Sources/UttrflowPipeline/DictationController.swift:386-390`) and a change of activation mode (`adopt`, line 151). Every other way the dictation can end leaves it `true`:

- **Menu bar → Stop Dictation**, the clipboard panel's dictate button and the failure notice's Retry all go through `toggleFromControl` → `toggleListening` (lines 298-305), which calls `pipeline.finishRecording()` and never touches `isHandsFree` (`Sources/Uttrflow/AppDelegate.swift:1260-1261`, `:966`, `:1812`).
- **The 4-minute cap**: `finishAtTheLimit` (lines 334-338) finishes the recording and leaves `isHandsFree` set.

With `isHandsFree` stale and the microphone closed:

- a **hold** does nothing: `press` returns at `guard !isHandsFree` (lines 197-200) without opening the microphone, and the release returns at line 349 because nothing is listening;
- a **double tap** does nothing either: both taps hit the same guard, and `endHold` returns before it can count a tap, so the gesture that would clear the flag can never complete.

The shortcut stays dead for the rest of the launch, or until the user happens to switch the activation mode in Settings and back. Nothing is shown: no failure, no notice.

## Why it matters

The double tap exists so a long dictation needs no held key. Ending that dictation from the menu bar is the obvious way for anyone who does not remember that a second double tap is the only "correct" stop, and the cap ends every dictation that runs long. Both leave dictation silently broken, which reads exactly like the "works sometimes" reports #141 was about. P1: the core flow stops working until relaunch.

## How to reproduce (headless, no microphone)

Add to `Tests/UttrflowPipelineTests/DictationControllerTests.swift`, using the file's own `makeHarness` and `tap` helpers:

```swift
@Test func handsFreeStoppedByAClickLeavesTheHoldWorking() async {
let harness = makeHarness()
await tap(harness)
harness.clock.advance(by: .milliseconds(120))
await tap(harness) // hands-free, listening
await harness.controller.toggleFromControl() // menu bar: Stop Dictation
harness.clock.advance(by: .seconds(2))
await harness.controller.handle(.pressed)
#expect(await harness.pipeline.currentState.isListening) // fails on main
}
```

On main (26d7bc1) this fails, and so does a double tap afterwards. The same shape in `DictationLimitWiringTests` (double tap, advance the `ManualClock` past `warnAfter` then `stopAfter`, wait for the insertion, press) also fails: the hold after the cap does not open the microphone.

## Acceptance criteria

- However a hands-free dictation ends (second double tap, menu bar or panel control, Retry, the cap, a cancel), the controller returns to a state where the next hold and the next double tap both work.
- Tests cover at least the click stop and the cap stop, each followed by a hold and by a double tap.
- `Docs/pipeline-gestures.md` says how a hands-free dictation can be ended.

Contributor guide

Open the contributing guide

Research direction

Start with Sources/UttrflowPipeline/DictationController.swift and the toggleFromControl and finishAtTheLimit paths, then review DictationControllerTests.swift and DictationLimitWiringTests for the existing harnesses. Run the named reproductions and verify that click stops and the four-minute cap leave both hold and double-tap gestures usable. Update Docs/pipeline-gestures.md to document the available ways to end hands-free dictation.

Written by the indexing model from the issue text.

Assessment

Tech stack
macos, swift
Domain
desktop, documentation, testing
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.