uttrflow / uttrflow/uttrflow-swift
Paste confirmation ignores cancellation and polls without its 40 ms delay
- Dominant language
- Swift
- Stars
- 4
- Forks
- 17
- Avg merge
- 3h 32m
- Merged PRs (30d)
- 277
Description
## Problem
`PasteConfirmation.waitFor` ignores errors from `clock.sleep(for: interval)` at `Sources/UttrflowInput/PasteConfirmation.swift:54`. With the default `ContinuousClock`, cancellation makes subsequent sleeps throw immediately. The loop still calls `focus.tail`, normalizes the returned text, and repeats until its elapsed-time budget expires.
A cancelled confirmation therefore loses its intended 40 ms pacing and continues unnecessary field reads. With a real Accessibility reader these are synchronous calls into another application; their cost limits the iteration rate, but cancellation does not stop them.
## Reproduction and evidence
Reviewed main commit `8d93f5b8574f3c7b8c30086d9c817bf7f5d4827d`. A Swift test exercised the unmodified production `PasteConfirmation` with its default clock and budget, and a thread-safe fake `AccessibilityFocus` that returns `.text("unchanged")` while counting calls:
1. Start `Task { await PasteConfirmation(focus: focus).waitFor("new words") }`.
2. Wait until the initial field read has happened, then cancel the task.
3. Await its value and assert that cancellation permits at most one additional in-flight read.
Observed **1,070,805 additional reads** and `.gaveUp` after **1.600000625 seconds**. The cancellation assertion failed; all **10 existing PasteConfirmation tests passed**. Their scripted clock advances simulated time without throwing on cancellation, so they do not cover this behavior.
This count is for a fast fake reader, not a measurement of native Accessibility throughput or app CPU usage.
## Caller and scope
`PasteboardTextInsertionEngine.insert` awaits this confirmation after sending the paste. Insertion is wrapped by `withStageTimeout` in `DictationPipeline`; the timeout helper cancels the work task when its deadline wins. Confirmation should cooperate if cancellation reaches it. A normal confirmation finishes before the 15-second stage limit; this test does not claim every dictation or the UI cancel action triggers the defect.
Unlike #213 (charging field-read time to the budget), this concerns losing the polling delay on cancellation. Unlike #228 (preventing late insertion side effects), it concerns the observation loop after a paste has already been sent.
## Acceptance criteria
- Exit confirmation promptly when cancelled, including cancellation during sleep, without further repeated field reads.
- Represent or propagate cancellation consistently through the insertion API; do not label cancellation as a successful confirmation.
- Add cancellation coverage using a clock that throws on cancellation, covering both pre-cancelled entry and cancellation while waiting.
- Preserve the existing elapsed-time, unreadable-field, and successful-arrival behavior.
Priority: P2 — avoidable resource use after cancellation.
Contributor guide
Research direction
Start with PasteConfirmation.waitFor in Sources/UttrflowInput/PasteConfirmation.swift:54 and the existing PasteConfirmation tests, then trace PasteboardTextInsertionEngine.insert through DictationPipeline's withStageTimeout. Add coverage for pre-cancelled entry and cancellation during sleep, ensuring cancellation stops further field reads without reporting success while preserving elapsed-time, unreadable-field, and successful-arrival behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- swift
- Domain
- accessibility, desktop, testing
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100