[fix] iOS dictation: the mic permission prompt survives the hand-off, and recent foregrounding buys a no-jump start - #281
Merged
Conversation
…regrounding buys a no-jump dictation start Two dictation reports, one first-run trap and one missing wake window: **"It says I have no microphone permission" — for a permission never asked.** On the first-ever dictation the app opens via parley://dictate and requestRecordPermission() puts up the system prompt — while DictationView, seeing state == .starting, headlines "Swipe back to your app". Obeying it backgrounds the app, which cancels the prompt as a refusal; the grant stays undetermined. The failed session arms the 30 s linger, so the next mic tap reaches the app over the Darwin channel in the background — where the prompt cannot be shown at all — and requestRecordPermission() fails again, this time publishing "Turn it on in Settings" to the keyboard for a permission the user was never actually asked for. Three changes close the loop: - launch() reads the standing grant first. Undetermined asks with a new awaitingMicPermission flag that swaps the dictation screen's guidance from "swipe back" to "allow the prompt" while the alert is up. A refusal in the prompt gets the Settings message; a prompt dismissed unanswered gets "tap the mic to try again", because the next tap will simply ask again. - The Darwin start path declines requests it cannot serve: anything short of .granted while backgrounded stays silent, so the keyboard's URL fallback brings the app forward where the prompt can actually be seen. - The error copy distinguishes denied from never-asked. **Every mic tap bounced through the app, even seconds after leaving it.** The no-jump Darwin start only works while this process is awake, and the linger that keeps it awake only armed after a dictation session ended. The most common beat — open Parley, switch to the app you're typing in, tap the keyboard's mic — always found the process suspended. The linger now arms on every didEnterBackground (and releases on didBecomeActive), so any recent foregrounding of Parley buys the next tap its ~30 s no-jump window. Thirty seconds is the platform's ceiling for a background task; past it the URL round trip remains the only way to wake the app. Verified: swift test (20), xcodebuild Debug for iPhone 17 Pro simulator (app + keyboard). New strings carry zh-Hant entries.
|
YJack0000
added a commit
that referenced
this pull request
Aug 20, 2026
One fix over 1.4: the first-run microphone permission prompt is no longer cancelled by the dictation screen's own swipe-back guidance, a backgrounded app no longer answers keyboard starts it cannot serve with a phantom permission error, and any recent foregrounding of Parley now buys the keyboard's next mic tap its ~30 s no-jump window (#281). 1.4.1 rather than build 11 of 1.4 because the release workflow requires the tag to equal CFBundleShortVersionString and ios-v1.4 is already spent.
Lanznx
added a commit
that referenced
this pull request
Aug 20, 2026
`project.yml` reached 1.4.1 and both tags went out, but `AppStore/metadata` still stopped at 1.3 — so the version about to be submitted has no What's New copy in either locale, and nothing tells a user why the globe key moved. 1.3 is what the App Store is serving, so these notes cover everything since it: the voice pane becoming a control panel (#266), the recording holding on to the microphone across foreground changes and the double-recording fix (#272), and the permission prompt surviving the keyboard hand-off (#281). The globe paragraph is written to be read by someone who read 1.3's. 1.3 announced the key on every device; this took it back where iOS already draws one, and saying so plainly is cheaper than letting people think it vanished. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 20, 2026
Lanznx
added a commit
that referenced
this pull request
Aug 20, 2026
`project.yml` reached 1.4.1 and both tags went out, but `AppStore/metadata` still stopped at 1.3 — so the version about to be submitted has no What's New copy in either locale, and nothing tells a user why the globe key moved. 1.3 is what the App Store is serving, so these notes cover everything since it: the voice pane becoming a control panel (#266), the recording holding on to the microphone across foreground changes and the double-recording fix (#272), and the permission prompt surviving the keyboard hand-off (#281). The globe paragraph is written to be read by someone who read 1.3's. 1.3 announced the key on every device; this took it back where iOS already draws one, and saying so plainly is cheaper than letting people think it vanished. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What this changes
Two fixes to the keyboard-dictation hand-off, both live on
ios-v1.4:DictationViewused to headline "Swipe back to your app" while the system microphone prompt was still up (state == .starting). Obeying it backgrounded the app, which cancels the prompt as a refusal — and the follow-up tap reached the app over the Darwin channel in the background, where the prompt can't be shown at all, sorequestRecordPermission()failed and the keyboard was told "Turn it on in Settings" for a permission the user was never actually asked for. Now:launch()reads the standing grant first; an undetermined grant asks under a newawaitingMicPermissionstate whose on-screen guidance is "Allow microphone access", not "swipe back".parley://dictatefallback brings the app forward where the prompt can be seen.didEnterBackground, not only after a dictation session. The most common beat — open Parley, switch to the app you're typing in, tap the keyboard's mic — always found the process suspended and bounced the user through the app; now any recent foregrounding of Parley buys the next tap its no-jump window. (30 s is the platform ceiling for a background task; beyond it the URL round trip remains the only wake path.)Why
Field report (via Jack): tapping the keyboard's mic always jumps to Parley even when the app was just open, and after coming back the keyboard claims there is no microphone permission. The permission claim traced to the prompt/guidance race above; the constant jumping traced to the linger only ever arming after a session, which first-time and casual flows never reach.
How it was verified
cd ios/ParleyKit && swift test— 20 tests passxcodegen generate+xcodebuild -scheme Parley -destination 'platform=iOS Simulator,name=iPhone 17 Pro' build— app + keyboard extension build cleanLocalizable.xcstringswith bothenandzh-Hantentries(Desktop checkboxes — tsc/vitest/tauri — not applicable: iOS-only change.)
Screenshots
The only UI change is the
DictationViewheadline while the system mic prompt is up ("Allow microphone access" + explainer instead of "Swipe back to your app"). The state exists only under a live, undetermined permission prompt with a signed-in account, which the screenshot demo harness can't fake — copy change is inDictationView.swift:110.