fix(typing): short-circuit verification for targets with no verifiable text - #809
fix(typing): short-circuit verification for targets with no verifiable text#809YuriNachos wants to merge 2 commits into
Conversation
|
The PR Policy check is blocking this PR because required template information is missing. Please update the PR description with:
Screenshots or video are required for UI, UX, settings, onboarding, overlay, menu bar, or visual behavior changes. If this PR has no visual changes, check the no-visual-change box in the template. If this remains incomplete for 48 hours after opening, the PR may be closed. |
Greptile SummaryThe PR avoids a five-second focused-text verification poll when the target exposes no verifiable accessibility or AppleScript text channel.
|
0af41ae to
05de5e8
Compare
… targets Consecutive dictations into a GPU-rendered/terminal target (e.g. Ghostty) that exposes no verifiable accessibility text channel stalled for seconds: withTemporaryPasteboardString holds the process-global pasteboard session semaphore across waitForFocusedTextVerification, which polled the full 5s timeoutMicros before returning .timeout because every if let-gated channel check fell through (all four channels nil) while the non-nil snapshot (real pid) skipped the only existing nil-snapshot early-out. Add a pure static predicate focusedTextSnapshotSupportsVerification (returns true iff at least one of value/selectedRange/appScriptValue/ appScriptSelectedRange is non-nil) and call it right after the guard let snapshot block: when it returns false, return .unavailable after a single bounded poll interval (usleep(min(timeoutMicros, 50_000))) instead of entering the 5s poll loop. Verifiable targets keep their exact current behaviour: the short-circuit sits strictly above the unchanged poll loop and only fires when all four channels are nil. Closes altic-dev#802 Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Yurii Chukhlib <yurii.chukhlib@viber.com>
…d add a behavioral test Address two review findings on the unverifiable-target short-circuit: 1. The 50 ms paste window was too aggressive. `action()` only enqueues Cmd+V (postToPid); the target reads NSPasteboard.general asynchronously on its own main thread, which under load (large paste, busy main thread during a build) can land hundreds of milliseconds later. Restoring the pasteboard after 50 ms could clobber the transcript before the target consumed it. Raise the window to a conservative 500 ms (pasteConsumeWindowMicros) — ~10x under the 5 s timeoutMicros, so the pasteboard session semaphore is still not held across multi-second windows (altic-dev#802), but the target gets a real consume chance. 2. The existing test only covered the classification predicate, not the short-circuit behavior. Add a behavioral test that drives the real waitForFocusedTextVerification with an unverifiable snapshot (real pid, all four verifiable channels nil) and asserts it returns .unavailable in bounded time well under timeoutMicros. This is an assertion-level guard: remove the short-circuit and it returns .timeout after ~5 s, failing both asserts. FocusedTextSnapshot / PasteVerificationResult / waitForFocusedTextVerification move from private to internal (module-scoped, not public) to make the deterministic short-circuit path exercisable headlessly; the live-AX poll loop is unchanged and not driven from tests. Closes altic-dev#802 Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Yurii Chukhlib <yurii.chukhlib@viber.com>
05de5e8 to
33cba5d
Compare
|
Saw the 1.6.9 fixes ship earlier today — nice to see the release come together. This PR has been green for most of a week now (build, SwiftLint, the policy check and Greptile all pass), so I wanted to ask whether anything in the approach gives you pause — for example the 500 ms paste-consume window before the clipboard is restored — or whether it just hasn't had a review pass yet. Happy to make the window tunable, extend the behavioural tests, or rebase onto the release if any of that would help. |
Description
On targets that expose no verifiable accessibility text — GPU-rendered terminals such as Ghostty —
waitForFocusedTextVerificationpolled for the full 5 stimeoutMicroswhile holding the process-global pasteboard session semaphore. That is the multi-second stall measured in #802 (sub-second → ~8.9 s) whenever dictation was injected into such a target.The fix short-circuits verification when none of the four verifiable focused-text channels is present (
value,selectedRange,appScriptValue,appScriptSelectedRangeallnil): sleep a pasteboard-consume window, then release the session as.unavailable. Verifiable targets — any one channel present — keep their existing fast path unchanged.The consume window is 500 ms (
pasteConsumeWindowMicros).action()only enqueues Cmd+V viapostToPid; the target then readsNSPasteboard.generalasynchronously on its own main thread, and under load (a large paste, a busy main thread during a build) that read can land hundreds of milliseconds later — so restoring the prior pasteboard too eagerly would clobber the transcript before the target consumes it. 500 ms is a conservative floor for a busy terminal to service the paste, yet ~10× under the 5 s timeout, so the #802 stall is gone.Diff:
Sources/Fluid/Services/TypingService.swiftplus one test file.Type of Change
Related Issue or Discussion
Closes #802.
Testing
swiftlint --strict --config .swiftlint.yml Sources→ 0 violations, 0 serious in 139 filesxcodebuild test -project Fluid.xcodeproj -scheme Fluid -destination 'platform=macOS,arch=arm64' CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO -skip-testing:FluidDictationIntegrationTests/DictationE2ETests/testDictationEndToEnd_whisperTiny_transcribesFixtureTwo new cases, both in
TypingServiceTransientPasteboardTests(suite passes in full):testFocusedTextSnapshotWithoutVerifiableChannelShortCircuitsVerification— pins the predicate: all-four-nil is unverifiable; any single non-nil channel is verifiable.testWaitForFocusedTextVerificationShortCircuitsUnverifiableTargetInBoundedTime— behavioural: drives the realwaitForFocusedTextVerificationwith an unverifiable snapshot (real pid, all four channels nil) and asserts it returns.unavailablein undertimeoutMicros / 2. Remove the short-circuit and it polls to.timeoutat ~5 s, failing both asserts.Two unrelated tests are red on my machine only, for the environment reasons this repo's own prerequisites document, and both are red on a clean
mainhere too:HotkeyShortcutTests.testKeyboardPayloadIgnoresStrayMouseButtonField— asserts the ASCII glyph at a keyCode; my host has a non-U.S. input source active.DictationE2ETests.testAppPromptBinding_defaultFallbackIgnoresGlobalSelection— reads realcom.FluidApp.appdefaults, which are customised on this machine.Neither touches the typing-verification path. CI runs on a clean U.S.-layout runner, so both should be green there.
Screenshots / Video
This is a backend typing-verification timing path — no view, layout, styling, settings, overlay or menu-bar surface is touched. The user-observable effect is latency: dictating into Ghostty returns in well under a second instead of ~8.9 s.
Notes
main; only the all-channels-nil case takes the new branch.pasteConsumeWindowMicros) so it is easy to tune if a slower target turns up.privatedeclarations (FocusedTextSnapshot,PasteVerificationResult,waitForFocusedTextVerification) becameinternalso the short-circuit path can be exercised by tests; none of them are public API.