From df7d9fa21be5869010f1036e60eaaf4cfdffa9f3 Mon Sep 17 00:00:00 2001 From: Cindy Zhang Date: Mon, 26 Oct 2026 17:19:52 -0700 Subject: [PATCH 1/2] spec(AST-094): a scroll-edge pan may cancel rubber-band moves from the first move (FR19, DEC-18) --- .../specs/AST-094-touch-presentations/spec.md | 71 ++++++++++++------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/docs/specs/AST-094-touch-presentations/spec.md b/docs/specs/AST-094-touch-presentations/spec.md index 94fda268d7a00..9da940a60cbf4 100644 --- a/docs/specs/AST-094-touch-presentations/spec.md +++ b/docs/specs/AST-094-touch-presentations/spec.md @@ -266,12 +266,17 @@ a measured, sourced difference exists. read for the row swipe, as its record states. A recognizer that begins MUST claim its touches by cancelling `touchmove` through a non-passive listener on its element; a watcher, which never claims, is exempt (DEC-7). A `touchmove` of a touch no recognizer owns MUST NOT be cancelled, with - two exceptions. While a scroll lock under a modal presentation holds the + three exceptions. While a scroll lock under a modal presentation holds the page, it MAY cancel the moves of a one-finger touch that no recognizer owns when that pan would scroll the page; a second finger is never cancelled (FR10), and the page keeps pinch-zoom. Once its press is delivered, a control that keeps its press through a drag (FR11) owns its touch and MAY - cancel that finger's moves. A + cancel that finger's moves. A scroll-edge pan, such as a sheet's pull + from a scroll area at its edge (FR14), MAY cancel its touch's moves from the + first move, only while every scroller under the finger sits at its edge and + the finger moves away from that edge, and only the moves the browser would + rubber-band, never a real scroll; the pan itself still begins at FR17's + 10 pt, as UIKit's sheet pull does (DEC-18). A recognizer that competes with native scrolling MUST decide within 10 px of travel, before the browser commits to the scroll; a screen-edge pan, whose band leaves the browser no horizontal scroll, decides within 15 px (FR18). Only an element with a @@ -528,30 +533,30 @@ FR33 apply to menus only. A mouse keeps today's behavior. ## Verification -| Contract | Verification | Representative states | Mutation or failure expectation | -| --------------- | ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| FR1, FR2 | Value check over every measured value and its uses | every value; a guess and its measurement task; a platform-specific value; two components playing the same motion | an unsourced value, an unknown source kind, a guess without a listed measurement, an unsourced platform split, or two values for one use fails | -| FR3 | Unit tests of the spring, decay, rubber band and projection math against an independent integration and values read from UIKit | under-, critically and overdamped springs; release velocities; both rates; overscroll depths | changing a formula or a constant breaks the agreement with UIKit's values | -| FR4 | Role unit tests over every touch motion | each role at its table value; a theme that writes `motion` | a literal played in place of a role, a value that differs from the table, or a theme that changes a role before the public set is decided fails | -| FR5 | Browser proof: an animation keeps advancing frame by frame through a 1 s main-thread block, with no layout or paint per frame | Chromium phone emulation; WebKit | a script-driven or layout-animating implementation freezes or lays out | -| FR6 | Unit and browser tests catching an animation mid-flight and retargeting it | catch at peak velocity; retarget with a new spring | a layout read, a jump, or a lost velocity fails | -| FR7, FR8 | Component browser tests and storyboards with and without Reduce Motion | entrance, exit, move; a drag and its lift under Reduce Motion | a missing exit, any travel, scale or spring under Reduce Motion other than a coast after a throw and its settle at an edge (`design:reduced-motion` DR2), or an opacity fade over 200 ms fails | -| FR9, FR10 | Recognizer unit tests on synthetic streams and browser tests with real touch input | drag, second finger, pinch, page zoom, main-thread block | a second finger that breaks a drag, or a page pinch blocked other than by the author's `touch-action`, fails | -| FR11, FR12 | Press and long-press tests on synthetic streams with explicit timestamps | inside and outside a scroll view; a drag the browser takes; a lift inside; a menu row under a finger; a press on a row of a menu whose rows fit and of one that scrolls | the wrong profile's timing, a press that survives the browser taking the touch, or a control activated other than by the browser's click, a row of a menu whose rows fit that waits 150 ms, or one of a scrolling menu that does not, fails | -| FR13 | Resolver unit tests and a server-render test | coarse and fine pointer; narrow, wide and short; server render; hydrating render | a component that answers the conditions differently, or a touch presentation in the server or hydrating render, fails | -| FR14 | Browser tests of a sheet over a scrolled list | scrolled and at its top | a midway handoff fails | -| FR15–FR17 | Recognizer unit tests on synthetic streams with explicit timestamps | exclusive, simultaneous, require-to-fail, nested; a watcher beside a claiming winner; a blocked main thread; a pan at 10 pt per axis | a wrong winner, a late deadline, or a translation that keeps the hysteresis, or a watcher failed or cancelled by a claiming winner fails | -| FR18–FR22 | Browser tests with real touch input (`spec:AST-013` FR10) | per-axis pans beside native scrolling, page zoom, cancelled touches, clicks after taps, keyboard activation, a modal scroll lock, a control that keeps its press through a drag; a still finger with sub-pixel jitter over a scroll view; a finger moved 9 px and 10 px straight, and (7, 7) and (8, 8) diagonally; a recognizer on an element whose author `touch-action` is narrower, including one without `pinch-zoom`; a screen-edge pan landing at 40 px and at 41 px from the edge, at 14 px and 15 px of travel, and a horizontal scroll that starts outside the band | a ghost click, a scroll or zoom blocked other than by the author's `touch-action`, or a cancelled unowned touchmove outside FR19's two exceptions, a tap cancelled by sub-pixel movement or by under 10 px of travel, a scroll that does not take the touch at 10 px, or a derived `touch-action` wider than the author's, a screen-edge pan that claims outside its band or before 15 px, or has not claimed at 15 px, or an author scroll outside the band that is blocked fails | -| FR23 | Haptics tests on the recorded moments | tap, drag, lift | a haptic outside a native moment fails | -| FR24 | Resource tests counting listeners, observers and timers | fine primary pointer; coarse pointer enabled and disabled; many instances; unmount | a touch listener on a fine pointer, a per-instance `document` or `window` listener, or a leak fails | -| FR25 | Component tests of each moved default and its `presentation` value | default; `presentation` set to the released surface | a moved default that `presentation` does not restore, or a public threshold, fails | -| FR26–FR28, FR30 | Menu press tests on synthetic touch streams and a real-Chromium touch test | a slide from one row to another; a lift outside; a cancel; a second finger; edge autoscroll | an act on the press-start row, two acts for one gesture, a lift outside that closes, or a lit row after a cancel fails | -| FR29 | Trigger tests with explicit timestamps | a press in a fixed bar; a press in scrolling content held 149 and 150 ms; a quick tap in scrolling content; a scroll taking the touch before 150 ms; a drift of 12 and 14 pt; moves before and after the handoff; `presentation="popover"` | a fixed-bar press that waits, a held finger's scroll-view open before 150 ms or after a scroll, a quick tap that does not open at its lift, an open past the drift allowance, a lift that acts before the handoff, or a `popover` trigger that opens on press fails | -| FR31–FR33 | Browser tests and storyboards under a finger | a menu, a Popover and a ContextMenu lift: open, close, a pan outside, a tap outside, a pinch over the surface; a mouse | a surface that does not grow from and shrink into its trigger, a pan outside that leaves a menu open, a Popover whose dismissal changed, a pinch blocked other than by the author's `touch-action`, a tap outside that reaches the page, or a changed mouse path fails | -| FR34, FR35 | Browser tests and storyboards under a finger | a sub-menu drilling in and its Back row returning; a tap and a drag during a popover's and a menu's growth; a pinch during growth; a mouse | a drill-in that leaves the parent's rows in place, a change between levels that does not play on its roles, a tap during growth that closes, scrolls or moves focus, a pinch blocked other than by the author's `touch-action`, or a changed mouse path fails | -| FR36–FR40 | Release tests on synthetic touch streams with explicit timestamps, per use | below and past each commit point; a flick that projects past it; a slow drag past it; a release moving back; Reduce Motion; keyboard and mouse paths | a decision that changes mid-settle, a release moving back that commits, a settle that does not start at the release velocity, or an action with no keyboard or mouse path fails | -| FR41 | Browser tests with real touch input (`spec:AST-013` FR10) | a tap; a long press on a ContextMenu trigger, a DropdownMenu trigger the caller renders, a row with a context menu, and a slider thumb; a long press on a checkbox row's label and on plain text; a cancelled press; mouse and keyboard selection | a tap that selects, a hold with an action that starts a selection or callout, a label or plain text whose long press does not select natively, a suppression that outlives the press, or a change to native selection elsewhere or to mouse, keyboard, or assistive-technology selection fails | -| all | iOS simulator: the UIKit reference capture and the same gesture in Safari; real iOS Safari and Android Chrome devices | per component | an unmatched curve or timing is a parity defect | +| Contract | Verification | Representative states | Mutation or failure expectation | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FR1, FR2 | Value check over every measured value and its uses | every value; a guess and its measurement task; a platform-specific value; two components playing the same motion | an unsourced value, an unknown source kind, a guess without a listed measurement, an unsourced platform split, or two values for one use fails | +| FR3 | Unit tests of the spring, decay, rubber band and projection math against an independent integration and values read from UIKit | under-, critically and overdamped springs; release velocities; both rates; overscroll depths | changing a formula or a constant breaks the agreement with UIKit's values | +| FR4 | Role unit tests over every touch motion | each role at its table value; a theme that writes `motion` | a literal played in place of a role, a value that differs from the table, or a theme that changes a role before the public set is decided fails | +| FR5 | Browser proof: an animation keeps advancing frame by frame through a 1 s main-thread block, with no layout or paint per frame | Chromium phone emulation; WebKit | a script-driven or layout-animating implementation freezes or lays out | +| FR6 | Unit and browser tests catching an animation mid-flight and retargeting it | catch at peak velocity; retarget with a new spring | a layout read, a jump, or a lost velocity fails | +| FR7, FR8 | Component browser tests and storyboards with and without Reduce Motion | entrance, exit, move; a drag and its lift under Reduce Motion | a missing exit, any travel, scale or spring under Reduce Motion other than a coast after a throw and its settle at an edge (`design:reduced-motion` DR2), or an opacity fade over 200 ms fails | +| FR9, FR10 | Recognizer unit tests on synthetic streams and browser tests with real touch input | drag, second finger, pinch, page zoom, main-thread block | a second finger that breaks a drag, or a page pinch blocked other than by the author's `touch-action`, fails | +| FR11, FR12 | Press and long-press tests on synthetic streams with explicit timestamps | inside and outside a scroll view; a drag the browser takes; a lift inside; a menu row under a finger; a press on a row of a menu whose rows fit and of one that scrolls | the wrong profile's timing, a press that survives the browser taking the touch, or a control activated other than by the browser's click, a row of a menu whose rows fit that waits 150 ms, or one of a scrolling menu that does not, fails | +| FR13 | Resolver unit tests and a server-render test | coarse and fine pointer; narrow, wide and short; server render; hydrating render | a component that answers the conditions differently, or a touch presentation in the server or hydrating render, fails | +| FR14 | Browser tests of a sheet over a scrolled list | scrolled and at its top | a midway handoff fails | +| FR15–FR17 | Recognizer unit tests on synthetic streams with explicit timestamps | exclusive, simultaneous, require-to-fail, nested; a watcher beside a claiming winner; a blocked main thread; a pan at 10 pt per axis | a wrong winner, a late deadline, or a translation that keeps the hysteresis, or a watcher failed or cancelled by a claiming winner fails | +| FR18–FR22 | Browser tests with real touch input (`spec:AST-013` FR10) | per-axis pans beside native scrolling, page zoom, cancelled touches, clicks after taps, keyboard activation, a modal scroll lock, a control that keeps its press through a drag; a still finger with sub-pixel jitter over a scroll view; a finger moved 9 px and 10 px straight, and (7, 7) and (8, 8) diagonally; a recognizer on an element whose author `touch-action` is narrower, including one without `pinch-zoom`; a screen-edge pan landing at 40 px and at 41 px from the edge, at 14 px and 15 px of travel, and a horizontal scroll that starts outside the band; a sheet's pull from a list at its top, and from a list scrolled away from its top | a ghost click, a scroll or zoom blocked other than by the author's `touch-action`, or a cancelled unowned touchmove outside FR19's two exceptions, a tap cancelled by sub-pixel movement or by under 10 px of travel, a scroll that does not take the touch at 10 px, or a derived `touch-action` wider than the author's, a screen-edge pan that claims outside its band or before 15 px, or has not claimed at 15 px, or an author scroll outside the band that is blocked, or a scroll-edge pan that cancels a move a scroller could scroll, or begins before 10 pt, fails | +| FR23 | Haptics tests on the recorded moments | tap, drag, lift | a haptic outside a native moment fails | +| FR24 | Resource tests counting listeners, observers and timers | fine primary pointer; coarse pointer enabled and disabled; many instances; unmount | a touch listener on a fine pointer, a per-instance `document` or `window` listener, or a leak fails | +| FR25 | Component tests of each moved default and its `presentation` value | default; `presentation` set to the released surface | a moved default that `presentation` does not restore, or a public threshold, fails | +| FR26–FR28, FR30 | Menu press tests on synthetic touch streams and a real-Chromium touch test | a slide from one row to another; a lift outside; a cancel; a second finger; edge autoscroll | an act on the press-start row, two acts for one gesture, a lift outside that closes, or a lit row after a cancel fails | +| FR29 | Trigger tests with explicit timestamps | a press in a fixed bar; a press in scrolling content held 149 and 150 ms; a quick tap in scrolling content; a scroll taking the touch before 150 ms; a drift of 12 and 14 pt; moves before and after the handoff; `presentation="popover"` | a fixed-bar press that waits, a held finger's scroll-view open before 150 ms or after a scroll, a quick tap that does not open at its lift, an open past the drift allowance, a lift that acts before the handoff, or a `popover` trigger that opens on press fails | +| FR31–FR33 | Browser tests and storyboards under a finger | a menu, a Popover and a ContextMenu lift: open, close, a pan outside, a tap outside, a pinch over the surface; a mouse | a surface that does not grow from and shrink into its trigger, a pan outside that leaves a menu open, a Popover whose dismissal changed, a pinch blocked other than by the author's `touch-action`, a tap outside that reaches the page, or a changed mouse path fails | +| FR34, FR35 | Browser tests and storyboards under a finger | a sub-menu drilling in and its Back row returning; a tap and a drag during a popover's and a menu's growth; a pinch during growth; a mouse | a drill-in that leaves the parent's rows in place, a change between levels that does not play on its roles, a tap during growth that closes, scrolls or moves focus, a pinch blocked other than by the author's `touch-action`, or a changed mouse path fails | +| FR36–FR40 | Release tests on synthetic touch streams with explicit timestamps, per use | below and past each commit point; a flick that projects past it; a slow drag past it; a release moving back; Reduce Motion; keyboard and mouse paths | a decision that changes mid-settle, a release moving back that commits, a settle that does not start at the release velocity, or an action with no keyboard or mouse path fails | +| FR41 | Browser tests with real touch input (`spec:AST-013` FR10) | a tap; a long press on a ContextMenu trigger, a DropdownMenu trigger the caller renders, a row with a context menu, and a slider thumb; a long press on a checkbox row's label and on plain text; a cancelled press; mouse and keyboard selection | a tap that selects, a hold with an action that starts a selection or callout, a label or plain text whose long press does not select natively, a suppression that outlives the press, or a change to native selection elsewhere or to mouse, keyboard, or assistive-technology selection fails | +| all | iOS simulator: the UIKit reference capture and the same gesture in Safari; real iOS Safari and Android Chrome devices | per component | an unmatched curve or timing is a parity defect | ## Decision log @@ -702,6 +707,20 @@ iOS 26.2 simulator measures it. Rejected: the 150 ms delay on every menu row. +### DEC-18 — A scroll-edge pan may hold the page's rubber band from the first move + +**Reference:** `spec:AST-094/DEC-18` +**Decider:** `cixzhang`, `2026-10-26` + +When every scroller under the finger is at its edge and the finger pulls away +from it, the browser would rubber-band the page before the pan reaches its +10 pt start, so a sheet's pull would visibly fight the page. Cancelling only +those moves, from the first, matches UIKit's sheet pull, and a real scroll is +never cancelled. The trade-off: a list inside a sheet cannot pull to refresh +from its top. + +Rejected: cancelling moves only after the pan begins. + ## Open questions - **OQ1 — Scroll view bounce-back curve.** What curve brings a scroll view From e42ad377dd26c4e33ceabb0d50c09d5c8f8b418f Mon Sep 17 00:00:00 2001 From: Cindy Zhang Date: Mon, 26 Oct 2026 21:32:41 -0700 Subject: [PATCH 2/2] =?UTF-8?q?spec(AST-094):=20FR18=E2=80=93FR22=20row=20?= =?UTF-8?q?counts=20three=20exceptions;=20the=20scroll-edge=20pan=20uses?= =?UTF-8?q?=20FR14's=20wording?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../specs/AST-094-touch-presentations/spec.md | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/docs/specs/AST-094-touch-presentations/spec.md b/docs/specs/AST-094-touch-presentations/spec.md index 9da940a60cbf4..2d1e602a7b34d 100644 --- a/docs/specs/AST-094-touch-presentations/spec.md +++ b/docs/specs/AST-094-touch-presentations/spec.md @@ -274,7 +274,7 @@ a measured, sourced difference exists. cancel that finger's moves. A scroll-edge pan, such as a sheet's pull from a scroll area at its edge (FR14), MAY cancel its touch's moves from the first move, only while every scroller under the finger sits at its edge and - the finger moves away from that edge, and only the moves the browser would + the drag moves toward that edge (FR14), and only the moves the browser would rubber-band, never a real scroll; the pan itself still begins at FR17's 10 pt, as UIKit's sheet pull does (DEC-18). A recognizer that competes with native scrolling MUST decide within 10 px of @@ -533,30 +533,30 @@ FR33 apply to menus only. A mouse keeps today's behavior. ## Verification -| Contract | Verification | Representative states | Mutation or failure expectation | -| --------------- | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| FR1, FR2 | Value check over every measured value and its uses | every value; a guess and its measurement task; a platform-specific value; two components playing the same motion | an unsourced value, an unknown source kind, a guess without a listed measurement, an unsourced platform split, or two values for one use fails | -| FR3 | Unit tests of the spring, decay, rubber band and projection math against an independent integration and values read from UIKit | under-, critically and overdamped springs; release velocities; both rates; overscroll depths | changing a formula or a constant breaks the agreement with UIKit's values | -| FR4 | Role unit tests over every touch motion | each role at its table value; a theme that writes `motion` | a literal played in place of a role, a value that differs from the table, or a theme that changes a role before the public set is decided fails | -| FR5 | Browser proof: an animation keeps advancing frame by frame through a 1 s main-thread block, with no layout or paint per frame | Chromium phone emulation; WebKit | a script-driven or layout-animating implementation freezes or lays out | -| FR6 | Unit and browser tests catching an animation mid-flight and retargeting it | catch at peak velocity; retarget with a new spring | a layout read, a jump, or a lost velocity fails | -| FR7, FR8 | Component browser tests and storyboards with and without Reduce Motion | entrance, exit, move; a drag and its lift under Reduce Motion | a missing exit, any travel, scale or spring under Reduce Motion other than a coast after a throw and its settle at an edge (`design:reduced-motion` DR2), or an opacity fade over 200 ms fails | -| FR9, FR10 | Recognizer unit tests on synthetic streams and browser tests with real touch input | drag, second finger, pinch, page zoom, main-thread block | a second finger that breaks a drag, or a page pinch blocked other than by the author's `touch-action`, fails | -| FR11, FR12 | Press and long-press tests on synthetic streams with explicit timestamps | inside and outside a scroll view; a drag the browser takes; a lift inside; a menu row under a finger; a press on a row of a menu whose rows fit and of one that scrolls | the wrong profile's timing, a press that survives the browser taking the touch, or a control activated other than by the browser's click, a row of a menu whose rows fit that waits 150 ms, or one of a scrolling menu that does not, fails | -| FR13 | Resolver unit tests and a server-render test | coarse and fine pointer; narrow, wide and short; server render; hydrating render | a component that answers the conditions differently, or a touch presentation in the server or hydrating render, fails | -| FR14 | Browser tests of a sheet over a scrolled list | scrolled and at its top | a midway handoff fails | -| FR15–FR17 | Recognizer unit tests on synthetic streams with explicit timestamps | exclusive, simultaneous, require-to-fail, nested; a watcher beside a claiming winner; a blocked main thread; a pan at 10 pt per axis | a wrong winner, a late deadline, or a translation that keeps the hysteresis, or a watcher failed or cancelled by a claiming winner fails | -| FR18–FR22 | Browser tests with real touch input (`spec:AST-013` FR10) | per-axis pans beside native scrolling, page zoom, cancelled touches, clicks after taps, keyboard activation, a modal scroll lock, a control that keeps its press through a drag; a still finger with sub-pixel jitter over a scroll view; a finger moved 9 px and 10 px straight, and (7, 7) and (8, 8) diagonally; a recognizer on an element whose author `touch-action` is narrower, including one without `pinch-zoom`; a screen-edge pan landing at 40 px and at 41 px from the edge, at 14 px and 15 px of travel, and a horizontal scroll that starts outside the band; a sheet's pull from a list at its top, and from a list scrolled away from its top | a ghost click, a scroll or zoom blocked other than by the author's `touch-action`, or a cancelled unowned touchmove outside FR19's two exceptions, a tap cancelled by sub-pixel movement or by under 10 px of travel, a scroll that does not take the touch at 10 px, or a derived `touch-action` wider than the author's, a screen-edge pan that claims outside its band or before 15 px, or has not claimed at 15 px, or an author scroll outside the band that is blocked, or a scroll-edge pan that cancels a move a scroller could scroll, or begins before 10 pt, fails | -| FR23 | Haptics tests on the recorded moments | tap, drag, lift | a haptic outside a native moment fails | -| FR24 | Resource tests counting listeners, observers and timers | fine primary pointer; coarse pointer enabled and disabled; many instances; unmount | a touch listener on a fine pointer, a per-instance `document` or `window` listener, or a leak fails | -| FR25 | Component tests of each moved default and its `presentation` value | default; `presentation` set to the released surface | a moved default that `presentation` does not restore, or a public threshold, fails | -| FR26–FR28, FR30 | Menu press tests on synthetic touch streams and a real-Chromium touch test | a slide from one row to another; a lift outside; a cancel; a second finger; edge autoscroll | an act on the press-start row, two acts for one gesture, a lift outside that closes, or a lit row after a cancel fails | -| FR29 | Trigger tests with explicit timestamps | a press in a fixed bar; a press in scrolling content held 149 and 150 ms; a quick tap in scrolling content; a scroll taking the touch before 150 ms; a drift of 12 and 14 pt; moves before and after the handoff; `presentation="popover"` | a fixed-bar press that waits, a held finger's scroll-view open before 150 ms or after a scroll, a quick tap that does not open at its lift, an open past the drift allowance, a lift that acts before the handoff, or a `popover` trigger that opens on press fails | -| FR31–FR33 | Browser tests and storyboards under a finger | a menu, a Popover and a ContextMenu lift: open, close, a pan outside, a tap outside, a pinch over the surface; a mouse | a surface that does not grow from and shrink into its trigger, a pan outside that leaves a menu open, a Popover whose dismissal changed, a pinch blocked other than by the author's `touch-action`, a tap outside that reaches the page, or a changed mouse path fails | -| FR34, FR35 | Browser tests and storyboards under a finger | a sub-menu drilling in and its Back row returning; a tap and a drag during a popover's and a menu's growth; a pinch during growth; a mouse | a drill-in that leaves the parent's rows in place, a change between levels that does not play on its roles, a tap during growth that closes, scrolls or moves focus, a pinch blocked other than by the author's `touch-action`, or a changed mouse path fails | -| FR36–FR40 | Release tests on synthetic touch streams with explicit timestamps, per use | below and past each commit point; a flick that projects past it; a slow drag past it; a release moving back; Reduce Motion; keyboard and mouse paths | a decision that changes mid-settle, a release moving back that commits, a settle that does not start at the release velocity, or an action with no keyboard or mouse path fails | -| FR41 | Browser tests with real touch input (`spec:AST-013` FR10) | a tap; a long press on a ContextMenu trigger, a DropdownMenu trigger the caller renders, a row with a context menu, and a slider thumb; a long press on a checkbox row's label and on plain text; a cancelled press; mouse and keyboard selection | a tap that selects, a hold with an action that starts a selection or callout, a label or plain text whose long press does not select natively, a suppression that outlives the press, or a change to native selection elsewhere or to mouse, keyboard, or assistive-technology selection fails | -| all | iOS simulator: the UIKit reference capture and the same gesture in Safari; real iOS Safari and Android Chrome devices | per component | an unmatched curve or timing is a parity defect | +| Contract | Verification | Representative states | Mutation or failure expectation | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FR1, FR2 | Value check over every measured value and its uses | every value; a guess and its measurement task; a platform-specific value; two components playing the same motion | an unsourced value, an unknown source kind, a guess without a listed measurement, an unsourced platform split, or two values for one use fails | +| FR3 | Unit tests of the spring, decay, rubber band and projection math against an independent integration and values read from UIKit | under-, critically and overdamped springs; release velocities; both rates; overscroll depths | changing a formula or a constant breaks the agreement with UIKit's values | +| FR4 | Role unit tests over every touch motion | each role at its table value; a theme that writes `motion` | a literal played in place of a role, a value that differs from the table, or a theme that changes a role before the public set is decided fails | +| FR5 | Browser proof: an animation keeps advancing frame by frame through a 1 s main-thread block, with no layout or paint per frame | Chromium phone emulation; WebKit | a script-driven or layout-animating implementation freezes or lays out | +| FR6 | Unit and browser tests catching an animation mid-flight and retargeting it | catch at peak velocity; retarget with a new spring | a layout read, a jump, or a lost velocity fails | +| FR7, FR8 | Component browser tests and storyboards with and without Reduce Motion | entrance, exit, move; a drag and its lift under Reduce Motion | a missing exit, any travel, scale or spring under Reduce Motion other than a coast after a throw and its settle at an edge (`design:reduced-motion` DR2), or an opacity fade over 200 ms fails | +| FR9, FR10 | Recognizer unit tests on synthetic streams and browser tests with real touch input | drag, second finger, pinch, page zoom, main-thread block | a second finger that breaks a drag, or a page pinch blocked other than by the author's `touch-action`, fails | +| FR11, FR12 | Press and long-press tests on synthetic streams with explicit timestamps | inside and outside a scroll view; a drag the browser takes; a lift inside; a menu row under a finger; a press on a row of a menu whose rows fit and of one that scrolls | the wrong profile's timing, a press that survives the browser taking the touch, or a control activated other than by the browser's click, a row of a menu whose rows fit that waits 150 ms, or one of a scrolling menu that does not, fails | +| FR13 | Resolver unit tests and a server-render test | coarse and fine pointer; narrow, wide and short; server render; hydrating render | a component that answers the conditions differently, or a touch presentation in the server or hydrating render, fails | +| FR14 | Browser tests of a sheet over a scrolled list | scrolled and at its top | a midway handoff fails | +| FR15–FR17 | Recognizer unit tests on synthetic streams with explicit timestamps | exclusive, simultaneous, require-to-fail, nested; a watcher beside a claiming winner; a blocked main thread; a pan at 10 pt per axis | a wrong winner, a late deadline, or a translation that keeps the hysteresis, or a watcher failed or cancelled by a claiming winner fails | +| FR18–FR22 | Browser tests with real touch input (`spec:AST-013` FR10) | per-axis pans beside native scrolling, page zoom, cancelled touches, clicks after taps, keyboard activation, a modal scroll lock, a control that keeps its press through a drag; a still finger with sub-pixel jitter over a scroll view; a finger moved 9 px and 10 px straight, and (7, 7) and (8, 8) diagonally; a recognizer on an element whose author `touch-action` is narrower, including one without `pinch-zoom`; a screen-edge pan landing at 40 px and at 41 px from the edge, at 14 px and 15 px of travel, and a horizontal scroll that starts outside the band; a sheet's pull from a list at its top, and from a list scrolled away from its top | a ghost click, a scroll or zoom blocked other than by the author's `touch-action`, or a cancelled unowned touchmove outside FR19's three exceptions, a tap cancelled by sub-pixel movement or by under 10 px of travel, a scroll that does not take the touch at 10 px, or a derived `touch-action` wider than the author's, a screen-edge pan that claims outside its band or before 15 px, or has not claimed at 15 px, or an author scroll outside the band that is blocked, or a scroll-edge pan that cancels a move a scroller could scroll, or begins before 10 pt, fails | +| FR23 | Haptics tests on the recorded moments | tap, drag, lift | a haptic outside a native moment fails | +| FR24 | Resource tests counting listeners, observers and timers | fine primary pointer; coarse pointer enabled and disabled; many instances; unmount | a touch listener on a fine pointer, a per-instance `document` or `window` listener, or a leak fails | +| FR25 | Component tests of each moved default and its `presentation` value | default; `presentation` set to the released surface | a moved default that `presentation` does not restore, or a public threshold, fails | +| FR26–FR28, FR30 | Menu press tests on synthetic touch streams and a real-Chromium touch test | a slide from one row to another; a lift outside; a cancel; a second finger; edge autoscroll | an act on the press-start row, two acts for one gesture, a lift outside that closes, or a lit row after a cancel fails | +| FR29 | Trigger tests with explicit timestamps | a press in a fixed bar; a press in scrolling content held 149 and 150 ms; a quick tap in scrolling content; a scroll taking the touch before 150 ms; a drift of 12 and 14 pt; moves before and after the handoff; `presentation="popover"` | a fixed-bar press that waits, a held finger's scroll-view open before 150 ms or after a scroll, a quick tap that does not open at its lift, an open past the drift allowance, a lift that acts before the handoff, or a `popover` trigger that opens on press fails | +| FR31–FR33 | Browser tests and storyboards under a finger | a menu, a Popover and a ContextMenu lift: open, close, a pan outside, a tap outside, a pinch over the surface; a mouse | a surface that does not grow from and shrink into its trigger, a pan outside that leaves a menu open, a Popover whose dismissal changed, a pinch blocked other than by the author's `touch-action`, a tap outside that reaches the page, or a changed mouse path fails | +| FR34, FR35 | Browser tests and storyboards under a finger | a sub-menu drilling in and its Back row returning; a tap and a drag during a popover's and a menu's growth; a pinch during growth; a mouse | a drill-in that leaves the parent's rows in place, a change between levels that does not play on its roles, a tap during growth that closes, scrolls or moves focus, a pinch blocked other than by the author's `touch-action`, or a changed mouse path fails | +| FR36–FR40 | Release tests on synthetic touch streams with explicit timestamps, per use | below and past each commit point; a flick that projects past it; a slow drag past it; a release moving back; Reduce Motion; keyboard and mouse paths | a decision that changes mid-settle, a release moving back that commits, a settle that does not start at the release velocity, or an action with no keyboard or mouse path fails | +| FR41 | Browser tests with real touch input (`spec:AST-013` FR10) | a tap; a long press on a ContextMenu trigger, a DropdownMenu trigger the caller renders, a row with a context menu, and a slider thumb; a long press on a checkbox row's label and on plain text; a cancelled press; mouse and keyboard selection | a tap that selects, a hold with an action that starts a selection or callout, a label or plain text whose long press does not select natively, a suppression that outlives the press, or a change to native selection elsewhere or to mouse, keyboard, or assistive-technology selection fails | +| all | iOS simulator: the UIKit reference capture and the same gesture in Safari; real iOS Safari and Android Chrome devices | per component | an unmatched curve or timing is a parity defect | ## Decision log @@ -712,9 +712,10 @@ Rejected: the 150 ms delay on every menu row. **Reference:** `spec:AST-094/DEC-18` **Decider:** `cixzhang`, `2026-10-26` -When every scroller under the finger is at its edge and the finger pulls away -from it, the browser would rubber-band the page before the pan reaches its -10 pt start, so a sheet's pull would visibly fight the page. Cancelling only +When every scroller under the finger is at its edge and the drag moves toward +that edge, the browser would otherwise rubber-band the page before the pan +reaches its 10 pt start (a reading of Mobile Safari's overscroll, not a +measurement), so a sheet's pull would visibly fight the page. Cancelling only those moves, from the first, matches UIKit's sheet pull, and a real scroll is never cancelled. The trade-off: a list inside a sheet cannot pull to refresh from its top.