[fix] the voice pane swipes from anywhere, not only from the record button - #405
Merged
Merged
Conversation
…utton The pane track read its swipe with a plain `.gesture`, which loses to whatever the finger lands on. A `Button` holds the touch until it lifts, so a swipe that began on a key only reached the track at release and the track never followed the finger. The zero-distance drag behind ⌫ took the touch outright. On the voice pane, where the record button is the one big target a thumb drags across and leaves, that read as "only the record button swipes". The track now reads the touch alongside the keys (`simultaneousGesture`) and disables the panes while its drag is in flight, so the key under the finger is cancelled once the swipe engages, the way a scroll view cancels a button's touch. A tap still presses and fires the key. `RepeatingKey` keeps its press in a `@GestureState`, which resets on that cancellation, so a swipe that starts on ⌫ does not leave the repeater deleting. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
✅ SonarQube Quality Gate passed — pathorsAI_parley0 open issues on this PR. |
4 tasks
YJack0000
added a commit
that referenced
this pull request
Sep 22, 2026
…he panes are tabs (#406) * [feature] the 注音 pane types like the system keyboard: toneless continuous input, delete under ㄦ Closes #400. The composer holds a buffer of up to six syllables and segments a run of untoned symbols on its own, so ㄋㄧㄏㄠ reads as ㄋㄧ ㄏㄠ and commits as 你好 with no tone key between them. The dictionary gains `~`-prefixed toneless rows (the union of a reading's five tones, ordered by corpus frequency) because the first tone is written with no mark and could not double as "tone not typed yet". The candidate bar follows the oldest pending syllable; tapping one commits it and moves on; return commits everything; punctuation from the symbol planes commits first. Delete moves from the function row to the eleventh column of the fourth symbol row, under ㄦ, where the system 注音 keyboard keeps it. Rows two and three are staggered by thirds rather than centred, and 123 / return are two and a half keys wide, as on the system keyboard. * [chore] iOS 1.16 (build 31): release notes, and the 注音 pane design as it now is Three things ship in 1.16, and all three are keyboard-side. The 注音 pane takes toneless continuous typing (#400). The composer holds an ordered buffer of up to six syllables instead of one, so a symbol that cannot extend the last syllable starts a new one rather than overwriting a slot — the rule that used to turn ㄋㄧ followed by ㄏ into ㄏㄧ, and the reason a sentence could not be typed without a tone key between every syllable. Tones now sharpen the guess instead of gating the next syllable, the candidate bar serves the oldest pending syllable, and the dictionary carries `~`-prefixed toneless rows because first tone is written with no mark and so cannot double as the toneless key. Delete moves to the eleventh column of the fourth symbol row, where the system keyboard keeps it, and rows 2 and 3 are staggered rather than centred. #405 makes the pane track read the touch alongside the keys, so a swipe between the voice, English and 注音 panes works from anywhere on the keyboard and does not type the key it started on. #404 stops the keyboard's mic tap taking a detour through Parley on every tap: a backgrounded Parley now tries the microphone before declining, and only the starts iOS actually refuses fall back to opening the app. Version bump: CFBundleShortVersionString 1.15 → 1.16 and CFBundleVersion 30 → 31 across all three targets in project.yml, with the Info.plists regenerated by xcodegen. What's New — 1.16 is written in both locales; asc_submit.py parses both and they are far under Apple's 4,000-character limit. * [feature] the 注音 pane predicts by phrase, and the panes are tabs Two lone 聲母 now offer the word: ㄋㄏ is 你好 before a vowel or tone is typed, the way the system keyboard has it. A phrase table generated from McBopomofo's BPMFMappings.txt (libtabe BSD readings, attributed in THIRD-PARTY.md) is matched by prefix within each syllable and indexed by the first symbol of the first two; phrases lead the bar, single characters follow, and return commits a greedy longest-phrase walk. The generator scores by corpus count plus character frequency and gives a short list of conversational words a floor, because the corpus is news and this keyboard types messages. Both tables warm on a background queue when the 注音 pane becomes current. The mode strip's dots become named tabs (語音 / 英文 / 注音): in use nobody took the dots for a control. The swipe is unchanged. The mic chip loses its minutes to make the row fit at 320pt. The composition chip hugs its text instead of taking the whole cap. Design doc, What's New (both locales) and the dictionary's doc comment updated to match. * [fix] candidates in the 注音 bar are separated, so two words stop reading as one The bar now mixes one- and two-character candidates; at 2pt spacing 會出好處會場 read as a single string. Each candidate gets an 11pt gutter and a hairline between neighbours, as the system keyboard spaces them. * [refactor] the two 注音 generators share one data module Sonar flagged the phrase generator for copying the dict generator's download, parsing and validation helpers (14.6% duplicated lines), a 21-complexity parse function and two nested ternaries. The shared parts move to scripts/zhuyin-data.mjs; both generators still write byte-identical resources.
This was referenced Sep 22, 2026
[feature] iOS 1.16: 注音 pane matches the system keyboard, no-jump fix, swipe fix — on TestFlight
#407
Closed
YJack0000
added a commit
that referenced
this pull request
Sep 24, 2026
…ecord button (#420) A fully transparent point of a keyboard extension never receives the touch. The voice pane is mostly empty space, so a swipe only started on a drawn control, which is the owner's report that survived #405: its gesture arbitration was right, but the touch never reached it. `contentShape` cannot fix this from inside SwiftUI; a 1% white fill on the pane track does, and also covers the gaps between keys on the typing panes. Verified on the iPhone 17 Pro simulator (iOS 26.3): before, a swipe from the voice pane's blank area did nothing while one from the record button switched panes. After, a swipe from the blank area, from the record button, and from between two rows of English keys all switch panes, taps still type, and the fill is invisible in light and dark mode. Co-authored-by: Claude Opus 5.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.
Why
Report from the owner: swiping switches keyboard panes, but on the voice pane only the record button responds. Swiping anywhere else there does nothing.
The pane track in
KeyboardRootViewread its swipe with a plain.gesture, and a plain gesture loses to whatever the finger lands on. AButtonholds the touch until it lifts, so a swipe that began on a key reached the track only at release, and the track never followed the finger. The zero-distance drag behind ⌫ (RepeatingKey) took the touch outright. That arbitration has been in place since #266 (715e1a1), which put the drag on the track. #397 did not touch it.The track now reads the touch alongside the keys (
simultaneousGesture) and disables the panes for as long as its drag is in flight. Once the swipe engages at 24pt, the key under the finger is cancelled the way a scroll view cancels a button's touch, so the swipe neither types the key nor fires the record button. A tap still presses and fires.RepeatingKeykeeps its press in a@GestureState, which resets on that cancellation, so a swipe that starts on ⌫ does not leave the repeater deleting.Scope
ios/Keyboard/KeyboardRootView.swift:.gesturebecomes.simultaneousGestureon the track, and the panes get.disabled(swiping)while the drag is in flight.ios/Keyboard/KeyboardKeys.swift:RepeatingKeymoves its press from@StateplusonEndedto@GestureStateplusonChange.docs/design/ios-voice-keyboard.md: the pane-track section records the routing.Out of scope: the UIKit globe key on home-button devices, which arbitrates at the UIKit level and is unchanged.
Tradeoffs
highPriorityGestureon the track also makes every start point swipe, but it withholds touch-down from the keys until the drag fails. Keys would stop darkening under the finger and the record button's press haptic would fire at release. Rejected.simultaneousGesturealone lets the key fire at release, because the key moves with the finger and the release lands inside it. That is what.disabled(swiping)prevents.Blast Radius
Every pane's keys, because the track wraps all of them. Taps are unchanged. What changes is a touch that starts on a key and travels sideways past 24pt: it used to stay with the key, now it moves the track and cancels the key. The candidate bar lives in the strip above the track and is not affected.
Verification
The iOS Simulator on this Mac could not run the keyboard, so the before and after runs are on a macOS SwiftUI harness that rebuilds the track's gesture graph (HStack track,
@GestureStateoffset,contentShape, a 24ptDragGesture, aButtonchild, a zero-distance drag child) and drives it with synthesized mouse events. The SwiftUI gesture arbitration is shared across platforms, but touch on iOS is not mouse on macOS, so treat this as strong evidence rather than a device pass.Before, with the track's
.gestureas onmain, an 80pt swipe from a blank area or the text moved the track and switched the pane. From theButtonthe button pressed and released, the track received no live updates, and the pane switched only at release. From the zero-distance drag child the child pressed and released and the track received nothing.After, with
simultaneousGestureplus.disabledwhile the drag is in flight, all four start points move the track live and switch the pane. The button presses on touch-down, is cancelled at 24pt, and does not fire. The repeating key presses on touch-down and its@GestureStateresets at 24pt, so the repeater stops.Taps (0pt) on the button and on the repeating key press and fire identically before and after.
xcodebuild -target ParleyKeyboard -sdk iphonesimulator26.2builds with the fix. The simulator itself is blocked on this machine:xcode-selectpoints at the Command Line Tools, the installed CoreSimulator framework is 1048 where Xcode 26.2 expects 1051.17.7, no iOS simulator runtime is installed (simctl runtime listis empty), andxcodebuild -runFirstLaunchwaits on an admin authorization this session cannot give.swift test --package-path ios/ParleyKitcannot run either: Xcode's toolchain fails to loadlibxcrun(arm64 slice only,/usr/bin/xcrunis arm64e) and itsXCTestCore.frameworkis missing, and the Command Line Tools toolchain fails to linkPackage.swiftagainst its ownPackageDescription. Please run the device pass on a phone before merging: swipe from the text, from a blank area, from ⌫, and from the record button on the voice pane, then tap each key and hold ⌫.🤖 Generated with Claude Code