Round-2 gate cert (iOS CORE): the touch predicate _isTouchLikeMessageViewport
used matchMedia('(pointer:coarse)'), which is true on BOTH Android and iOS. But
overflow-anchor is inert on iOS WebKit (the repo's own static/style.css mobile
content-visibility block documents this — it deliberately does not set
overflow-anchor:none because it is a no-op on iOS and re-opens the #4856/#5338
jump on Android). So on iOS the stale-anchor refusal fired but its premise (let
the native overflow-anchor layer hold the viewport) is false → a scrolled-up iOS
reader was left unheld after above-viewport growth, the same class as the round-1
desktop regression, one platform over.
Split the platform check: add _isIOSWebKit() (classic iPhone/iPod/iPad UA, plus
iPadOS 13+ which masquerades as MacIntel but has maxTouchPoints>1 unlike a real
Mac) and exclude it from _isTouchLikeMessageViewport. The refusal now fires ONLY
on Android (pointer:coarse AND overflow-anchor actually works); desktop and iOS
both keep the explicit semantic realign / absolute snapshot.top restore.
Tests: 3 new predicate cases (iPhone, iPadOS-as-Mac, Android control), all
mutation-checked — removing the _isIOSWebKit exclusion fails exactly the iOS
cases while Android/desktop stay green; broadening _isIOSWebKit to any touch
device fails the Android control. The Node harness forces the navigator mock via
Object.defineProperty because Node 18+ ships a built-in read-only navigator that
a plain assignment silently ignores.
Note: iOS Safari cannot be exercised by the Node harness (the 'native anchor
holds' postcondition is mocked, never observed), so this needs a real iOS-Safari
recording before merge per the gate cert.