feat(ios): read modal surfaces from a modal-scope pair

Merged
TH
20h ago
+624-121

PR 2 of 3 for modal-scoped alert on iOS. Stacks on #4124. Host-only: no runner, alert, or snapshot behavior change.

ModalSurface.read pairs a plain and an honor-modal tree. A surface is the children the honor-modal tree keeps under a node where it removes others. When a kept child holds its own surface, that inner surface replaces the outer one, so an underlying modal's controls stay out. Keyboard branches offer no actions. The outcome is modal(surfaces), noExclusion, or unknown(reason). An identical pair never reads as absent.

SnapshotPresentedSurface.classify takes the reading as presenting evidence. The golden table gains modalScopeExpected on two corpus-derived cases. The TS rule is unchanged.

RecordingSurface rootActions offeredExcluded
SpringBoard alert, folded and unfoldedAlert (innermost of 3 levels)Cancel, Opennone
Safari prompt, foldedSFDialogView's containerCancel, OpenBack, bookmark, add, Tabs, refresh
Safari prompt, unfoldedsameCancel, Open7 toolbar buttons
Settings Clear History sheetsheet containerdismiss popup, both close buttons, checkmark, 5 cells, Close All Tabs, Clear Historynone
SpringBoard home, Settings rootnoExclusionnone—

Remaining gaps (not recorded yet): iPad popovers, React Native Modal, SFSafariViewController, physical devices.

Checks: swift test, TS parity tests, check:affected. Tests catch mutations to nested replacement, keyboard skip, dismiss marker, and modal evidence.

šŸ¤– Generated with Claude Code