Skip to content

fix: make directional gesture presets orientation-aware - #73

Merged
onevcat merged 2 commits into
mainfrom
fix/gesture-orientation
Jul 28, 2026
Merged

onevcat merged 2 commits into
mainfrom
fix/gesture-orientation

Conversation

@onevcat

@onevcat onevcat commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Part of #66 — PR A of two. PR B (follow-up) adds --coordinate-space ui to swipe/touch.

Problem

gesture scroll-up/down/left/right and swipe-from-*-edge name visual directions, but their stroke math ran on a hardcoded 390×844 portrait canvas (iPhone 15) and the endpoints reached HID untransformed. On a rotated device the emitted axis is 90° (landscape) or 180° (upside-down) away from the preset's name — reproduced live on a landscape iPad: scroll-up moved nothing while a native-horizontal swipe scrolled the page vertically.

Fix

  • Preset math moves to visual space. New pure layer GestureOrientationMapping: visualSize resolves the canvas (explicit --screen-width/height per axis → calibrated uiSize(native:) → legacy 390×844 last), hidStroke carries UI-space endpoints across uiToFramebuffer — the same iOS tap coordinates are wrong when app orientation and Simulator window rotation differ #34 mapping tap selectors ride.
  • Lightweight calibration entry. AccessibilityFetcher.fetchOrientationCalibration does one tree XPC + ≤3 hit-test probes and skips the collapsed-children recovery walk entirely, so gestures never pay for the quadtree probing (hundreds of XPC round-trips on WebView-heavy screens).
  • Batch covered. The batch gesture step (its own raw path in Command+BatchConvertible) rides BatchContext.orientationCalibration — one calibration per run, shared with tap selector steps. An unreachable AX tree degrades to identity dispatch with a step-prefixed orientation_calibration_fallback advisory instead of failing the batch.
  • Never silent. Standalone fallback surfaces the advisory through the envelope (ExecutionResult now conforms to CommandAdvisoryProviding, registered in the exclude-from-data contract tests).
  • Scope held deliberately: explicit swipe/touch coordinates stay device-native portrait per the iOS tap coordinates are wrong when app orientation and Simulator window rotation differ #34 acceptance contract (opt-in visual space arrives in PR B); pinch/rotate presets stay raw — orientation-aware multi-touch needs every interpolated sample mapped, not just endpoints.

Testing

  • TDD: GestureOrientationMappingTests written against a stub reproducing the legacy behavior first — 23 failures across 4 orientations × 4 scrolls + edges (including portrait-on-iPad, pinning the canvas-default fix), then green after the real implementation. Endpoint expectations hand-derived from the uiToFramebuffer formulas.
  • BatchGestureOrientationTests: one calibration shared across directional steps, multi-touch never fetches, fetch failure records the advisory and still emits primitives.
  • Full suite: 1179 tests green.
  • Live (iPad Pro 13-inch M5, Safari 200-row page): portrait scroll-up unchanged (ROW-001→ROW-006, now with the true 1032×1376 canvas); landscape scroll-up scrolls ROW-001→ROW-005 where the pre-fix binary moved nothing — calibrator confirmed landscape-left in 2 probes and the dispatched HID endpoints (645,688)→(387,688) match the unit-test expectations exactly.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Rir4WwrPShf5DYzziJ8RyC

gesture scroll-*/swipe-from-*-edge name visual directions, but their
stroke math ran on a hardcoded 390x844 portrait canvas and the
endpoints reached HID untransformed - on a rotated device the gesture
pointed 90/180 degrees away from its name (issue #66: scroll-up on a
landscape iPad scrolled nothing while a native-horizontal swipe
scrolled the list vertically).

Directional presets now compute in the calibrated visual space and
their endpoints cross uiToFramebuffer - the same issue #34 mapping tap
selectors ride. The canvas defaults to the auto-detected UI size of
the current orientation (--screen-width/height become a visual-space
viewport override; 390x844 survives only when the screen cannot be
probed). The batch gesture path rides the batch-wide calibration and
degrades to an identity dispatch with a recorded advisory when the AX
tree is unreachable. Calibration uses a new recovery-free fetch entry
(one tree XPC + at most 3 probes) so gestures never pay for the
collapsed-children quadtree walk.

Explicit swipe/touch coordinates stay device-native portrait per the
issue #34 contract; pinch/rotate presets stay raw (orientation-aware
multi-touch needs per-sample mapping and is left out of scope).

Verified live on an iPad Pro 13-inch (M5): landscape scroll-up now
scrolls (ROW-001 -> ROW-005) where the pre-fix binary moved nothing,
with the HID endpoints matching the unit-test expectations exactly;
portrait behavior unchanged. 1179 tests green.

Part of #66 (PR A; PR B adds --coordinate-space ui to swipe/touch)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rir4WwrPShf5DYzziJ8RyC

Signed-off-by: onevcat <onevcat@gmail.com>
Review follow-up on the orientation fix: the --screen-width/height
help claimed visual-space auto-detection unconditionally, but the
pinch/rotate branch still runs on the raw device-native 390x844
canvas. Say so explicitly in both the ios subcommand and the
top-level forwarder help, and scope the CHANGELOG entry to
single-finger presets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rir4WwrPShf5DYzziJ8RyC

Signed-off-by: onevcat <onevcat@gmail.com>
@onevcat
onevcat merged commit 03c7291 into main Jul 28, 2026
4 checks passed
@onevcat
onevcat deleted the fix/gesture-orientation branch July 28, 2026 09:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant