Skip to content

fix(ios): touch-control UX fixes, a Return key and 8× local zoom - #27

Draft
raiseCatError wants to merge 12 commits into
release/app-store-rcfrom
feature/ios-control-surface-ux
Draft

raiseCatError wants to merge 12 commits into
release/app-store-rcfrom
feature/ios-control-surface-ux

Conversation

@raiseCatError

@raiseCatError raiseCatError commented Oct 25, 2026 •

Copy link
Copy Markdown
Owner

iPhone/iPad touch-control fixes found by a simulator UX review of the shipped v1.20.0 iOS source. The branch is based on release/app-store-rc (cbbad7f), not on #24, #25 or #26. No protocol, networking, pairing, streaming or security changes. Every change is iOS UI code or the pure layout and preferences helpers in Shared/.

The full write-up (feature inventory, findings, Control Surface assessment, hardware checklist): ux-review/ios-control-surface.md

What changes

  1. The ⌘ palette no longer covers the iPhone tray. Avoid Notch (on by default) moved the tray off the home indicator or notch but left the palette behind, so it covered half the tray on every Face ID iPhone. The palette now moves with the tray.
  2. The iPhone Main Tray fits the screen. Nine 40 pt chips (396 pt) ran off 390 pt and 402 pt phones, and the outer halves of ⌘ and Settings didn't respond to touches. Chips now shrink just enough to fit (36 pt on 390 pt, 38 pt on 402 pt, never below 30 pt).
  3. Tray controls are real buttons for VoiceOver, Voice Control and Switch Control. They now have the button trait and an action: modifiers latch, palette keys fire, and Settings, esc, tab, Dock, Keyboard and Move View work. Also fixes the Explore Demo card, which hid its button behind a plain group.
  4. The keyboard bar shows in Demo Mode. Demo passed the keyboard's frame instead of the visible area above it, so the modifier/esc/tab bar sat behind the keyboard.
  5. iPad starts on the radial bottom-right Custom layout for fresh installs and Demo Mode. Strip and Overlay remain in Settings, saved choices are untouched, and iPhone is unchanged. Confirmed with the product owner during this work.
  6. Demo banner at accessibility text sizes. It overflowed and covered Exit Demo; it's now capped at xxLarge and the chips at xxxLarge.
  7. A Return key (follow-up). One plain Return press (HID 40) from the Function Tray (its own group after Undo/Redo; visible on fresh installs and on upgraded v1.20.0 trays nobody customized; added hidden to customized trays, which keep their layout), the keyboard bar (between tab and Hide Keyboard; keys shrink to 35 pt on 375 pt and 37 pt on 390 pt so eight fit), and the radial iPad layout's Two-Hand Assist arc (new layouts; saved ones keep their arc). A latched ⌘ or ⇧ is held on the Mac and merges into the press, so ⌘ then ↵ is ⌘↵. Sent only while input is allowed; the Mac enforces the grant too. Function Tray chips and the iPad Wheel palette's wedges now perform their action for VoiceOver, Voice Control and Switch Control (checked in source; XCTest can't trigger accessibility actions).
  8. Local viewport zoom to 8× (was 3×), plus a fix for a pre-existing bug it exposed: pan limits used the rotated picture's bounding box, so a tilted picture dragged diagonally could leave the screen entirely. The clamp now works in the picture's own rotated frame (identical at 0°/90°/180°) and keeps in-range states exactly. Zoom is a layer transform: stream resolution, decoding and networking are unchanged, and Zoom While Typing keeps its 1.5× limit.
Before After
⌘ palette, iPhone 17
Tray, iPhone 17e (390 pt)
Keyboard bar (Demo)
Return: Function Tray, iPhone 17e —
Return: keyboard bar, iPhone SE (375 pt) —
Return: radial arc + 8× pan, iPad Pro 13
iPad default

Tests

  • New unit tests (macOS test target): palette carried with the tray (portrait and landscape), fitted chip size, Demo keyboard geometry, iPad initial layout and the "saved choice is kept" rule. The new tests failed against the old code where applicable.
  • New UI test ReceiverControlsTourTests: a Demo Mode tour plus testTrayControlsAreAccessibilityButtons. On the shipped build that test fails with "Command is not exposed as a button"; on this branch it passes.

Follow-up: Return and 8× zoom

Commits af961c5 (Return) and 1cc4930 (8× zoom and the rotated-pan clamp), plus docs (7410a74, 2ea0025). New tests: Return's default group, single plain press, saved-profile upgrade (an untouched v1.20.0 tray shows Return in every slot; a customized one keeps its layout and gains Return hidden; idempotent; a later hide sticks; commit f81898b), keyboard-bar order and fit at 375/390/402/440 pt, HID 40 → kVK_Return with latched ⌘/⇧, the radial assist arc; for zoom, 1×/3×/4×/6×/8×, continuous anchored pinching, extreme and NaN/∞ pans, a 7.5° angle grid with exact idempotence, the tilted-diagonal regression, reset/restore, pointer round trips, and orientation/window/keyboard rebasing. UI tests testReturnSendsAPressWithKeyboardClosedAndOpen (taps Return in the tray and the keyboard bar and checks Demo received it) and testHighMagnificationTour.

Verified (iOS 26.5 simulators, Xcode 27.1 beta, one simulator at a time):

  • OpenSidecarMac full unit suite: 1,875 tests, 0 failures.
  • iOS build-for-testing succeeds.
  • ReceiverControlsTourTests (5 tests incl. the new two): pass on iPad Pro 13, iPhone 17e (390 pt), iPhone SE 3rd gen (375 pt) and iPhone 17 (402 pt).
  • DemoModeSmokeTests: pass on iPad Pro 13, iPhone 17 and iPhone 17e. On 17e, portrait failed once on the first launch after boot (the Explore Demo tap didn't register) and passed on rerun. On iPhone SE (375 pt), landscape can't walk Settings. This predates the branch: it's a test scrolling issue. The Gestures row sits under the pinned Search field, so the test's single swipe flings the list past it. With the same test on the same iOS 26.5 SE simulator, the shipped base cbbad7f and this head behaved identically: portrait 3/3 pass, landscape 0/3, the same list state at failure. For that comparison both located Explore Demo by identifier; on the base the unchanged test can't find the Explore Demo button at all (finding 5). Test left unchanged; details in the review doc.
  • Performance: see ux-review/ios-control-surface-performance.md. Local gesture math costs ~0.13 µs per update at any zoom; whether iOS composites a scaled/rotated video layer differently needs hardware. No streaming changes.
  • Not verified: anything on a physical device, including Return into a live Mac, VoiceOver/Voice Control/Switch Control activation, and zoom latency on a live stream. The review doc's checklist has the steps.

Verification (first round)

  • OpenSidecarMac full unit suite: 1,854 tests, 0 failures (run before the Dynamic Type commit, which is iOS view code only).
  • iOS simulator (iOS 27.1, Xcode 27.1 beta), on iPhone 17, iPhone 17e, iPad mini, iPad Air 11 and iPad Pro 13, portrait and landscape: the tour passes everywhere. iPad Pro 13 portrait failed once on the hold-vs-tap timing under load and passed on rerun. The existing DemoModeSmokeTests pass in portrait and landscape; on iOS 27.1 they couldn't find Explore Demo before the card fix.
  • Largest accessibility text size checked on iPhone 17 (Demo).
  • git diff --check is clean. Only the final tree was built; the intermediate commits weren't built one by one.
  • Not verified: touch feel, Apple Pencil, real latency, end-to-end streaming, VoiceOver/Voice Control/Switch Control on a device, and the live Request Input prompt at large text sizes. The checklist in the review doc covers these.

Notes

  • Control Surface mode: Video Off already turns the device into a trackpad and control panel, so this PR adds no new mode. The review suggests making Video Off easier to reach instead.
  • Left for follow-ups: a hint that makes Precise Control discoverable, the keyboard bar gap (check it on hardware first), and a smoke-test scrolling helper that works on 375 pt landscape. Wheel palette wedges now have assistive-technology actions (item 7); that's checked in source only, with on-device activation in the checklist.

…ves it

Avoid Notch lifts the iPhone tray off the home indicator (portrait) or the
notch (landscape), but the palette was avoided on its own and stayed where
the tray used to be, covering half the tray on every Face ID iPhone. Carry
the palette by the tray's offset, as the reserved-region pass already does.
… and Switch Control

The Main Tray, keyboard bar and iPad chips are drawn views whose taps
arrive through one hit-testing gesture, so assistive technologies saw
labelled text with no button trait and no action. Give each control the
button trait and its tap action directly (a modifier latches, as on a
quick tap); palette keys fire their shortcut.

The Explore Demo card wrapped its button in a separate accessibility
element, hiding the button trait (and the button from UI tests).

Adds a Demo Mode UI tour that captures the controls per device and
orientation and checks the tray controls are buttons.
Nine 40pt chips need 396pt, wider than a 390pt or 375pt iPhone in
portrait and taller than it in landscape, so Command and Settings ran
off the screen and out of the tray's touch area (the geometry clamps the
touch frame to the screen). Shrink the chips just enough to fit, never
below 30pt; trays that already fit are unchanged.
Demo Mode passed the keyboard's own frame where the control overlay
expects the visible area above it, so the modifier/esc/tab bar was laid
out behind the keyboard and the trays disappeared while typing. Hand the
overlay the same geometry a live session does.
A fresh iPad install (and Demo Mode) now opens on the radial Custom
layout around a bottom-right Settings anchor instead of Strip. Strip and
Overlay remain in Settings > Controls > Control Layout, and saved
preferences are never replaced. iPhone is unchanged.
At accessibility text sizes the fixed-height banner's text overflowed and
covered Exit Demo, and the status chips wrapped mid-word over the
desktop. Cap the banner at xxLarge and the chips at xxxLarge.
Feature inventory, findings with before/after simulator screenshots, the
Control Surface assessment and a hardware-only test checklist.
Return (HID usage 40, no modifiers of its own) is now a built-in Function
Tray action in its own group after Undo/Redo, a key in the keyboard bar
between tab and Hide Keyboard, and part of the radial iPad layout's
Two-Hand Assist arc. A latched modifier is held on the Mac and merges into
the press, so ⌘ then ↵ sends ⌘↵.

Fresh installs show it; saved Function Tray profiles keep their layout and
gain Return hidden, and saved Custom layouts keep their own assist arc. The
bar's keys shrink to fit eight (35 pt on 375 pt, 37 pt on 390 pt). The bar's
Return is sent only while input is allowed; the Mac enforces the grant too.

Function Tray chips and the iPad Wheel palette's wedges now perform their
action for VoiceOver, Voice Control and Switch Control instead of only
carrying a label; the iPad chip's ignored children had dropped its tap.
…creen

Manual viewport zoom now goes to 8× instead of 3×. It stays a layer
transform: stream resolution, decoding and networking are unchanged, and
Zoom While Typing keeps its own 1.5× limit.

Pan limits came from the rotated picture's bounding box, so a tilted
picture dragged along a diagonal could leave the screen entirely, more so
the further it was zoomed. The clamp now projects the view's center onto
the picture in the picture's own rotated frame, which is identical at 0°,
90° and 180°. An in-range state is kept exactly, so restore and relayout
never nudge it, and the at-rest snap is skipped when it would step the
center off a tilted picture's edge.

Tests cover 1×, 3×, 4×, 6× and 8×, continuous and anchored pinching,
extreme and non-finite pans, an angle grid with idempotence, reset and
restore, pointer round trips, and orientation, window and keyboard
changes; a Demo Mode UI tour pinches to 8×, rotates and pans.
Adds findings 8 (Return) and 9 (8× zoom and the rotated-pan clamp) to the
control surface review, with iPhone SE, iPhone 17e and iPad Pro 13
screenshots and new hardware checklist items. A new performance audit
lists the existing telemetry, software-only measurements (local gesture
math ~0.13 µs per update at any zoom), code-level findings, what needs a
device, and candidates for a separate performance PR.
@raiseCatError raiseCatError changed the title fix(ios): touch-control UX fixes from an iPhone/iPad simulator review fix(ios): touch-control UX fixes, a Return key and 8× local zoom Oct 26, 2026
The Settings walk in DemoModeSmokeTests fails in landscape on a 375 pt
iPhone SE on the shipped base and on this branch alike (portrait 3/3,
landscape 0/3 each, same list state at failure): the Gestures row sits
under the pinned Search field and the test's single swipe flings past it.
Also states that the Wheel and chip accessibility actions are checked in
source only.
Saving any receiver setting also writes the Function Tray profiles, so a
v1.20.0 tray nobody customized was stored and, on upgrade, treated like a
customized one: Return arrived hidden.

A saved tray that still matches v1.20.0's starting tray exactly (action
IDs, order, visibility and groups, with no renamed or restyled built-in)
now takes today's default, with Return shown. Any other tray keeps its
layout and gains Return hidden, as before. An upgraded tray contains
Return, so it never matches again: the upgrade is idempotent and a later
hide sticks. Tested against a literal v1.20.0 stored profile in every
slot.
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