Purpose
#2589's guard (@agent-device/contracts/tap-keyboard-occlusion, shipped by #2601/#2602) derives the visible keyboard's band from the captured tree and refuses a tap whose point lands inside it. On landscape iPhone the Apple capture hands over keyboard rects in the keyboard's own rotated space, so no band can be derived and the silent misfire stays open in that orientation.
The classifier now refuses to measure geometry that arrives that way — 342248ff0a added the "docked geometry is wider than it is tall" rule — which converts wrong refusals into honest fail-open. That is the stopgap. This issue is the fix: report keyboard-owned rects in the app's orientation space.
Observed
iPhone 17 Pro, iOS 26.2, system keyboard over the test app's Form screen, app viewport 874 x 402. The screenshot shows the keyboard spanning the full width across the bottom 327 pt (suggestion bar at y ~200..240, key rows below, dock row at the bottom edge).
The regular and the interactive projection report the same numbers:
| node |
role |
reported rect |
| key plane |
UIKeyboardLayoutStar |
x 2, y 75, 162 x 327 |
key q |
UIAccessibilityElementKBKey |
x 124, y 77, 35 x 72 |
key w |
UIAccessibilityElementKBKey |
x 124, y 149, 35 x 72 |
key t |
UIAccessibilityElementKBKey |
x 124, y 366, 35 x 36 |
dock Next keyboard |
UIKeyboardDockItemButton |
x 0, y 8, 65 x 68 |
QWERTY advances along y at fixed x, and successive key rows step x down instead of y up (a s d f at x 88, z x c at x 53, the 123/emoji/space row at x 18). The globe dock button reports y 8 while it is drawn at the bottom-left. Measured against those rects, a band covers a ~141 pt strip on the left edge from y 77 down: it would refuse app content near the top-left that the keyboard is nowhere near, and let the tab-bar item at (264, 360) tap straight through into a key. That pass-through was observed on device before the guard rule landed.
Portrait on the same device is correct: key plane x 0..402, y 583..816, and the dock buttons bottom out at 874 = the viewport's bottom edge. The same app, same keyboard, landscape only.
Required behavior
- Keyboard-owned nodes reach
snapshot, snapshot -i, and everything downstream in the same coordinate space as the app's root viewport, in every orientation the device can be in.
- The transform is applied where the rects are read, not in a consumer: the same tree must not mean two things depending on which rule reads it.
- Zero-area
Padding-Left / Padding-Right placeholder keys keep reporting as they do today; the classifier already ignores unusable rects.
Observable completion
- A landscape device snapshot of a raised keyboard whose key plane spans at least the app viewport's width, and whose dock button's y sits in the bottom third of the viewport.
- The landscape iOS case in
contracts/fixtures/tap-keyboard-occlusion-policy.json moves from undetermined to a measured band (occluded for a target under it), built from a captured tree rather than a modeled one.
- On device: landscape
press @ref on a tab-bar item behind the keyboard refuses with tap_keyboard_occludes_target, and a control visibly above the keyboard still presses.
- The wider-than-tall rule in
tap-keyboard-occlusion.ts stays for backends that report unusable geometry, but no longer fires for iOS landscape; say which backends still reach it in the comment that claims it.
Dependencies
Notes
Portrait iPhone, and the Android emulator's Gboard capture, already report usable geometry, so this is scoped to orientation handling on Apple mobile, not to keyboard ownership in general.
Purpose
#2589's guard (@agent-device/contracts/tap-keyboard-occlusion, shipped by #2601/#2602) derives the visible keyboard's band from the captured tree and refuses a tap whose point lands inside it. On landscape iPhone the Apple capture hands over keyboard rects in the keyboard's own rotated space, so no band can be derived and the silent misfire stays open in that orientation.The classifier now refuses to measure geometry that arrives that way —
342248ff0aadded the "docked geometry is wider than it is tall" rule — which converts wrong refusals into honest fail-open. That is the stopgap. This issue is the fix: report keyboard-owned rects in the app's orientation space.Observed
iPhone 17 Pro, iOS 26.2, system keyboard over the test app's Form screen, app viewport 874 x 402. The screenshot shows the keyboard spanning the full width across the bottom 327 pt (suggestion bar at y ~200..240, key rows below, dock row at the bottom edge).
The regular and the interactive projection report the same numbers:
UIKeyboardLayoutStarqUIAccessibilityElementKBKeywUIAccessibilityElementKBKeytUIAccessibilityElementKBKeyNext keyboardUIKeyboardDockItemButtonQWERTY advances along y at fixed x, and successive key rows step x down instead of y up (
a s d fat x 88,z x cat x 53, the123/emoji/space row at x 18). The globe dock button reports y 8 while it is drawn at the bottom-left. Measured against those rects, a band covers a ~141 pt strip on the left edge from y 77 down: it would refuse app content near the top-left that the keyboard is nowhere near, and let the tab-bar item at (264, 360) tap straight through into a key. That pass-through was observed on device before the guard rule landed.Portrait on the same device is correct: key plane x 0..402, y 583..816, and the dock buttons bottom out at 874 = the viewport's bottom edge. The same app, same keyboard, landscape only.
Required behavior
snapshot,snapshot -i, and everything downstream in the same coordinate space as the app's root viewport, in every orientation the device can be in.Padding-Left/Padding-Rightplaceholder keys keep reporting as they do today; the classifier already ignores unusable rects.Observable completion
contracts/fixtures/tap-keyboard-occlusion-policy.jsonmoves fromundeterminedto a measured band (occludedfor a target under it), built from a captured tree rather than a modeled one.press @refon a tab-bar item behind the keyboard refuses withtap_keyboard_occludes_target, and a control visibly above the keyboard still presses.tap-keyboard-occlusion.tsstays for backends that report unusable geometry, but no longer fires for iOS landscape; say which backends still reach it in the comment that claims it.Dependencies
Notes
Portrait iPhone, and the Android emulator's Gboard capture, already report usable geometry, so this is scoped to orientation handling on Apple mobile, not to keyboard ownership in general.