Skip to content

fix(ios): unify multi-touch gestures on two-finger synthesis + hinted unsupported errors#645

Merged
thymikee merged 3 commits into
mainfrom
fix/ios-rotate-gesture-synthesis
Jun 1, 2026
Merged

fix(ios): unify multi-touch gestures on two-finger synthesis + hinted unsupported errors#645
thymikee merged 3 commits into
mainfrom
fix/ios-rotate-gesture-synthesis

Conversation

@thymikee
Copy link
Copy Markdown
Member

@thymikee thymikee commented Jun 1, 2026

Draft pending on-device verification via the gesture lab (see checklist). Compiles (build-for-testing: TEST BUILD SUCCEEDED).

What

Make RunnerSynthesizedGesture (the two-finger XCTest event synthesis) the single iOS multi-touch engine, and drop the older incompatible models.

Gesture iOS tvOS macOS
pinch synthesis (#634) unsupported + hint unsupported + hint (was coordinate hack)
rotateGesture synthesis (was native XCUIElement.rotate) unsupported + hint unsupported + hint
transformGesture synthesis unsupported + hint unsupported + hint

swipe/scroll/pan/fling are unchanged — the daemon already maps them to single-finger drag, which is the correct primitive.

Why

Aligns with the design goal: every command returns a reliable outcome and a clear, hinted error. rotateGesture was the last iOS multi-touch path still on a model RN can't read; macOS pinch was a heuristic pretending to work.

Verification (blocking before un-drafting)

  • gesture-lab.ad on a simulator: gesture rotate 35 …"rotate changed yes" (the native path's failure mode)
  • same replay: gesture pinch 1.25 … → "pinch changed yes" (no regression)
  • pan/fling steps still pass
  • tvOS/macOS pinch/rotate return UNSUPPORTED_OPERATION with the new hint
  • build-for-testing green

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

Size Report

Metric Base Current Diff
JS raw 1.1 MB 1.1 MB -5.8 kB
JS gzip 360.9 kB 358.8 kB -2.1 kB
npm tarball 461.7 kB 459.8 kB -2.0 kB
npm unpacked 1.5 MB 1.5 MB -4.8 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 25.4 ms 25.4 ms -0.0 ms
CLI --help 39.3 ms 39.4 ms +0.1 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/9533.js -1.5 kB -499 B
dist/src/session.js -269 B -219 B
dist/src/cli.js -167 B -75 B
dist/src/args.js -228 B -63 B
dist/src/2415.js +238 B +59 B

thymikee added 2 commits June 1, 2026 18:25
… unsupported errors

Make RunnerSynthesizedGesture the single iOS multi-touch engine and drop the older
incompatible models:

- rotateGesture now drives the two-finger XCTest synthesis path (dx=dy=0, scale=1,
  degrees), mirroring the pinch migration in #634. The native XCUIElement.rotate(withVelocity:)
  injected a single synthetic rotation that React Native's rotation recognizer did not read
  reliably; synthesis fixes it. velocity is ignored on iOS (kept in the wire contract for
  compatibility; rotation direction comes from the sign of degrees).
- pinch is now synthesis on iOS and a clear UNSUPPORTED_OPERATION on tvOS/macOS. The macOS
  coordinate double-tap+drag heuristic (performCoordinatePinch) is removed: synthesis is
  iOS-only, so macOS multi-touch is reported honestly as unsupported rather than approximated.
- RunnerInteractionOutcome.unsupported now carries an actionable hint, mapped to ErrorPayload.hint
  (#639). Every unsupported gesture/tvOS path returns a concise message plus a next-step hint
  (existing messages kept verbatim).

Net: on iOS, pinch/rotate/transform all flow through one synthesis primitive. swipe/scroll/pan/
fling remain single-finger drags (correct, unchanged).

Coverage: examples/test-app/replays/gesture-lab.ad exercises pinch + rotate against the gesture
lab and asserts "pinch changed yes" / "rotate changed yes".
…s-only

Follow-through for removing the macOS coordinate pinch path (the runner now returns
UNSUPPORTED_OPERATION for macOS pinch): reject it at admission instead of round-tripping.

- capabilities.ts: pinch now matches rotate-gesture/transform-gesture (Android + iOS
  simulator only); macOS dropped. Removes the now-unused isMacOsOrMobileAppleSimulator helper.
- capabilities.test.ts: pinch expected unsupported on macOS and tvOS.
- website/docs/docs/commands.md: pinch listed for Android + iOS simulators only (removed from
  the macOS app-session list); documents that iOS rotate ignores the optional velocity arg
  (synthesis uses a fixed duration; direction comes from the sign of degrees).

Addresses PR #645 review HIGH #2 and MEDIUM #3.
@thymikee thymikee force-pushed the fix/ios-rotate-gesture-synthesis branch from 11bbacc to 7db7726 Compare June 1, 2026 16:45
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-06-01 17:30 UTC

@thymikee thymikee marked this pull request as ready for review June 1, 2026 16:47
@thymikee
Copy link
Copy Markdown
Member Author

thymikee commented Jun 1, 2026

Review addressed + on-device verification ✅

HIGH #1 — core behavior now verified. Ran examples/test-app/replays/gesture-lab.ad on an iPhone 17 simulator (Metro 8081):

pass 1/1  examples/test-app/replays/gesture-lab.ad  duration=21.59s
Test summary: 1 passed, 0 failed

That run executes gesture rotate 35 195 443wait "rotate changed yes" and gesture pinch 1.25wait "pinch changed yes" (plus pan/fling), so the synthesis-driven rotateGesture does drive RN's rotation recognizer where native XCUIElement.rotate did not. (The earlier AsyncStorage is null was a red herring: the dev build was loading a different app's bundle from a Metro on 8081; with the correct gesture-lab bundle served, it passes.)

HIGH #2 — macOS pinch follow-through (new commit 7db7726). You're right that the runner-side removal needed to be reflected upstream:

  • capabilities.ts: pinch now matches rotate-gesture/transform-gestureAndroid + iOS simulator only; macOS dropped. Since the capability matrix is the admission fail-fast (isCommandSupportedOnDevice), macOS pinch now fails fast instead of round-tripping to the runner. Removed the now-unused isMacOsOrMobileAppleSimulator helper.
  • capabilities.test.ts: pinch expected unsupported on macOS + tvOS.
  • website/docs/docs/commands.md: pinch listed for Android + iOS simulators only (removed from the macOS app-session line).

MEDIUM #3 — velocity. Documented in commands.md: iOS rotate ignores the optional velocity (synthesis uses a fixed duration; direction comes from the sign of degrees).

MEDIUM #4 (optional) — hint test. Deferred. As you noted in #6, parseRunnerResponse already maps hintAppError.details; happy to add a dedicated UNSUPPORTED_OPERATION+hint assertion in a follow-up if you'd like it in-tree.

MEDIUM #5 — fixed durationMs: 300. Left at parity with the pinch path; the lab passed at 35°. Flagged for revisiting if large-degree rotations flake.

Notes: branch rebased onto current main (post-#642). typecheck clean; capabilities unit test green (6/6). The 35 unrelated unit failures in my local run were pre-existing tool-spawn timeouts (Android SDK / sandbox), not from this change.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7db77267cd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/core/capabilities.ts
// path (RunnerSynthesizedGesture), which is iOS-only. macOS has no multi-touch synthesis, so
// it is excluded and fails fast at admission rather than round-tripping to an unsupported
// runner. Matches rotate-gesture / transform-gesture.
supports: (device) => device.platform === 'android' || isIosMobileSimulator(device),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Route macOS pinch to the hinted failure

For macOS app sessions, ensureGenericCommandReady in src/daemon/request-generic-dispatch.ts checks this capability before dispatch, so gesture pinch now fails at admission with the generic pinch is not supported on this device payload and never reaches the new runner branch that returns the macOS-specific message and hint. That undercuts the hinted unsupported behavior added for removing macOS pinch; either admission needs to return the same specific hint or the command needs to be allowed through to the hinted failure path.

Useful? React with 👍 / 👎.

…t admission

Removing macOS pinch from the capability matrix makes macOS pinch (and the already-excluded
rotate-gesture/transform-gesture on macOS/tvOS/physical iOS) fail fast in ensureGenericCommandReady
before reaching the runner. That left the runner's macOS-specific hint unreachable on the daemon
path, so callers only saw the generic "<cmd> is not supported on this device".

Add an optional unsupportedHint to the capability matrix and surface it at admission, so the
synthesis-only gestures fail fast (no runner round-trip) AND return an actionable hint pointing to
where they work (Android + iOS simulator). Applied to pinch / rotate-gesture / transform-gesture.

Addresses PR #645 review (P2: route macOS pinch to the hinted failure).
@thymikee
Copy link
Copy Markdown
Member Author

thymikee commented Jun 1, 2026

P2 (route macOS pinch to the hinted failure) — fixed in 567e4e8, kept fail-fast.

Rather than re-open the runner round-trip, the hint now lives at admission:

  • Added optional unsupportedHint(device) to the capability matrix and surfaced it in ensureGenericCommandReady (request-generic-dispatch.ts). The daemon error type already carries hint, and the CLI renders it.
  • Wired it for pinch / rotate-gesture / transform-gesture (they shared the same dead-runner-hint situation on macOS/tvOS/physical-iOS).

So macOS pinch now fails fast at admission (no runner round-trip) and returns an actionable hint — e.g. pinch is not supported on this device + hint: macOS automation has no multi-touch input — this gesture is supported on Android and the iOS simulator only. The runner-side macOS branch stays as a defensive fallback. Unit test added (capabilities.test.ts); typecheck clean.

@thymikee thymikee merged commit 3785a17 into main Jun 1, 2026
19 checks passed
@thymikee thymikee deleted the fix/ios-rotate-gesture-synthesis branch June 1, 2026 17:30
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