Repository navigation
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
There was a problem hiding this comment.
This pull request is on hold until an engineering owner decides whether the draft spec:AST-094 (FR1–FR23, DEC-1 and DEC-2) governs the gesture and motion work this stack adds; an engineering owner records that decision by commenting /approve-spec followed by the full head commit SHA, and the author may do so if they are an engineering owner. Whatever that decision, these changes are needed before approval:
- Build the
presentation="adaptive"compact test from the theme's compact definition (compactDefinition()onmain,spec:AST-012FR9) instead of a hard-coded 744 px width, which sends coarse-pointer screens 744 to 767 px wide to the popover. - Drop the
platformadaptation condition untilspec:AST-012FR2 adds it to its closed vocabulary. - Keep reduced-motion durations and springs out of the themable motion roles, as
design:reduced-motionDR2 and DR4 require. - Run
pr-interactionunder an existing CI owner whose join it can fail, perspec:AST-030FR10 and FR11. - Remove or qualify the new
astryx docs motionsection that says sheets and menus already play these springs, since no component reads them yet. - Against
mainthe pull request carries about 29,500 authored lines from the unmerged stack, so landing the predecessors first would leave only this commit to review.
[Automated review]
| export const TOUCH_QUERY = '(pointer: coarse)'; | ||
|
|
||
| /** The viewport is compact: layout behaviours. */ | ||
| export const COMPACT_QUERY = '(width < 744px), (height < 500px)'; |
There was a problem hiding this comment.
spec:AST-012 FR9 (and architecture:public-component-api INV17) on main say the touch presentation resolver uses the theme's one compact definition, width below the effective md point (768 by default) or height below 500, and keeps no size numbers of its own. With 744 here, a coarse-pointer screen 744 to 767 px wide (an iPad mini in portrait) gets the popover where FR9 gives the sheet. main exports compactDefinition() from theme/themeAdaptations.ts; building COMPACT_QUERY and COMPACT_TOUCH_QUERY from it would conform.
| * is everything else. Pair `ios` with `pointer: 'coarse'` for a touch | ||
| * profile, such as touch motion tuned per platform. | ||
| */ | ||
| platform?: 'ios' | 'other'; |
There was a problem hiding this comment.
spec:AST-012 FR2 closes the condition vocabulary ("Future environment fields require explicit additions to this closed vocabulary"), and neither main nor this pull request amends it, so platform needs that amendment first or should come out of this change.
| * Cross-Fade Transitions on (opacity CABasicAnimation 0.5 s, | ||
| * (0.42, 0, 0.58, 1)), Oct 23 2026 | ||
| */ | ||
| reducedMotionCrossFadeMs: 500, |
There was a problem hiding this comment.
This 500 ms value becomes the theme role --motion-timing-reduced-motion-cross-fade-ms. design:reduced-motion DR2 requires a reduced-motion cross-fade of 200 ms or less under a coarse pointer, and DR4 says theme values offer no way to keep a longer animation or springs under reduced motion. Excluding the reduced-motion entries from the themable roles (as press.scrollBrakeMs is excluded below) would keep the theme API within DR4.
| # them, so a change to an instrument is proven by its negative controls | ||
| # before a component spec relies on it. Chromium only on these runners; | ||
| # see the harness README for running WebKit. | ||
| pr-interaction: |
There was a problem hiding this comment.
spec:AST-030 FR10 and FR11: a new test type is not a new surface, and new coverage must join an existing owner rather than add a separately routed lane. This job routes itself and is not in the test join's needs, so a failing interaction proof fails no required context.
| content: [ | ||
| { | ||
| type: 'prose', | ||
| text: 'On a touch screen, sheets, alerts, menus, navigation and scrolling play the springs, durations and curves measured from iOS. Each value is a motion role named by its table path, such as `springs.sheetPresent` or `timing.curves.easeInOut`. A theme retunes a role through `motion.springs`, `motion.durations` or `motion.curves`, at its root or in an adaptation rule; a role it leaves alone keeps its measured default.', |
There was a problem hiding this comment.
No component reads the motion roles or the animator yet, so sheets, alerts, menus and navigation do not play these springs today and retuning springs.sheetPresent has no visible effect. Could this section say so, or wait until a component ships its touch presentation (as AST-094's current-state impact suggests)?
PR Analysis Report📚 Storybook Preview🧪 Sandbox PreviewComponent Size
Sizes are gzip. Import counts the JavaScript the import loads eagerly; CSS counts the package stylesheet rules referenced by the modules it can load from its own package, lazily loaded ones included. Bundle Size SummaryGzip figures measure each package's entry file, not the code an import pulls in.
Accessibility AuditStatus: No accessibility violations detected. Visual Regression55 of 502 shot(s) changed. A change here is a question, not a failure: check whether the after is the
and 35 more. Generated by PR Enrichment workflow | Storybook | Sandbox | View full report |
eaa20fb to
b72a600
Compare
…t reads its end before the arena's reset) into facebook#8336
…xposes no ontouchstart is taken once Chromium fixes ontouchstart when the page is created, so turning touch emulation on later sends Touch Events without it: the host reads Pointer Events for the finger and must not read its Touch Events too. A one-finger long press fails here, the finger tracked twice.
…from their pointer events install() reads a finger from its pointer events when the window has no ontouchstart, but the four touch handlers still took the Touch Events a browser sends anyway (Chromium after touch emulation turns on), tracking the finger twice, so a one-finger long press never recognized. Each touch handler now returns while usesTouchEvents is false, after noting the fingers down, as install()'s comment promised.
…ter events) into meta#8320
There was a problem hiding this comment.
This needs an engineering owner's decision before it can be approved. perEventPointer and actionGroup attach touch work where the primary pointer is fine, which spec:AST-094 FR19 and FR24 currently forbid, and the exceptions they rely on exist only in #8469 and #8622. An engineering owner records that decision by commenting /approve-spec 3d7fa2ebcbcb2089a5cf9b0cf63b19e641f3ff3c on this pull request, and the author may do so if they are an engineering owner; otherwise the two options wait for those pull requests. Whatever the decision, these changes are needed:
- Remove the
tracker.offsetBycall in therotationsetter (RotationGestureRecognizer.ts:56), which doubles a re-based rotation's release velocity, and assert the release value in its tests. - Add the real-touch browser cases that AST-094's FR18–FR22 verification row names: an edge pan on an element wider than the band, and a still finger with sub-pixel jitter over a scroll view.
- Reduce the 24,598-line compare by landing the predecessors and moving the motion, build-plugin, harness and finger-scroll pieces to their own pull requests.
- Fix the fine-pointer warning for action groups and the stale
GestureHost.tscomments, and name thetreeshakeCompensationdefault in the build changeset.
[Automated review]
| this.rotationValue = value; | ||
| // The velocity continues from the re-based value, without a jump: the | ||
| // samples shift with it (as the pinch's scale setter scales them). | ||
| this.tracker.offsetBy(delta, 0); |
There was a problem hiding this comment.
The tracker samples the raw lastAngle (lines 90 and 106), so shifting its samples here makes every re-base read as extra turn. A listener that sets rotation = 0 on each change releases at about twice the true speed: 4.363 rad/s for a 2.182 rad/s turn, against AST-100 FR2. Dropping this call (and delta) keeps the comment at 87–89 true. The tests at 62, 95 and 118 should then assert the release value, not only > 1.5.
| if (options.perEventPointer === true) { | ||
| this.perEventRecognizers.add(recognizer); | ||
| } else if (!this.isCoarse) { | ||
| this.warnFinePointer(); |
There was a problem hiding this comment.
An actionGroup attach also reaches this branch when the pointer is fine. The warning then says the attachment "stays inactive until the pointer is coarse", although the host serves the group (746–764). If the option survives, skip the warning for action groups.
| * input selectable); restored when the contact ends. Only where the | ||
| * primary pointer is coarse: a mouse never takes this lock, nor a finger | ||
| * or a pen where the primary pointer is fine. | ||
| * - Each element with a recognizer that can claim a touch while it moves (a |
There was a problem hiding this comment.
"Nothing else is ever cancelled" no longer holds. The kept press and the scroll-edge hold (1649–1659) cancel moves of a touch no recognizer has claimed yet, and a cancelled gesture now keeps a moving finger claimed until it lifts. Name FR19's three exceptions and the cancelled-claim keep here.
| <div id="hscroll" style="height: 300px; overflow-x: auto; overscroll-behavior: contain"> | ||
| <div style="width: 3000px; height: 300px; background: linear-gradient(90deg, #eef, #cce)">wide</div> | ||
| </div> | ||
| <div id="edge" style="position: fixed; left: 0; top: 0; width: 40.2px; height: 300px"></div>`; |
There was a problem hiding this comment.
This fixture's edge element is 40.2 px wide, within the band. AST-094's FR18–FR22 verification row also asks for a real-touch case on an element that reaches beyond the band (DEC-22: touch-action left at auto, a claim at 10 px), and for a still finger with sub-pixel jitter over a scroll view.
| '@astryxdesign/build': patch | ||
| --- | ||
|
|
||
| [fix] `astryxStylex` resolves `defineConsts` values read from another file. With the `{stylexOptions}` form (the one Storybook uses), a file that read only constants from a `.stylex.ts` module lost that import once StyleX compiled it, so the constants never reached the CSS: a constant used as a media-query key failed the build ("Invalid empty selector") and one used as a value shipped as a custom property nothing declares. In either form, a constant defined on the other side of the layer split, or in an Astryx package (compiled with the library class-name prefix) and read from an app file (compiled with its own), stayed unresolved. |
There was a problem hiding this comment.
Please also say that the {stylexOptions} form now defaults treeshakeCompensation to true, and that passing treeshakeCompensation: false restores the previous output (AST-017 FR3).
…xperimental touch exports The motion README's Files table is the union: main's rows, with meta#8663's resolveSnapPoint.ts and selectPage.ts, plus this branch's, in main's order. # Conflicts: # packages/core/src/motion/README.md
…on/README.md) into facebook#8309
There was a problem hiding this comment.
This needs an engineering owner's decision before it can be approved: the press tracker's perEventPointer and actionGroup attach touch work where the primary pointer is fine, which spec:AST-094 FR19 and FR24 rule out as they stand on main. An engineering owner needs to update FR19 and FR24 for the press-feedback exception (proposed in #8469) and provide the governing spec for action groups (proposed in #8622), or approve both here by commenting /approve-spec <full head SHA>, which the author may do as an engineering owner. Whatever the decision, these changes are needed:
- Cancel a claimed finger's
touchmovewhere the host now skips Touch Events (noontouchstart):cancelIfClaimedstill finds the finger only throughtouchIds, and the second-finger check sits behind the same early return. - Remove the
tracker.offsetBycall in therotationsetter, which doubles a re-based rotation's release velocity, and assert the release value in its tests. - Add the FR18–FR22 real-touch cases for an edge pan on an element wider than the band and for a still finger with sub-pixel jitter.
- Reduce the 24,648-line compare by landing the predecessors and splitting the separable pieces, name the
treeshakeCompensationdefault in the build changeset, and fix the action-group warning that says the attach stays inactive.
[Automated review]
| for (const touch of moved) { | ||
| const id = | ||
| this.touchIds.get(touch.identifier) ?? | ||
| (this.inputMode === 'pointer-events' |
There was a problem hiding this comment.
On the path this head adds (no ontouchstart, so onTouchStart returns before touchIds is filled), this lookup finds no finger and a claimed finger's touchmove is never cancelled, against spec:AST-094 FR19 ("A recognizer that begins MUST claim its touches by cancelling touchmove"). Keying the fallback on the channel the host actually reads covers both pointer-sourced paths:
const id =
this.touchIds.get(touch.identifier) ??
(!this.usesTouchEvents ? this.pointerFingerAt(touch) : undefined);| this.rotationValue = value; | ||
| // The velocity continues from the re-based value, without a jump: the | ||
| // samples shift with it (as the pinch's scale setter scales them). | ||
| this.tracker.offsetBy(delta, 0); |
There was a problem hiding this comment.
The tracker is fed the raw unwrapped angle (lines 90 and 106), so shifting its samples here reads every re-base as extra turn: 8 steps of 2° every 16 ms release 2.182 rad/s untouched and 4.363 rad/s when a listener sets rotation = 0 on each change, against spec:AST-100 FR2. Without this line (and delta) the setter no longer touches the tracker, so the re-based release equals the untouched one; a test asserting a release near 2.18 rad/s would catch it.
| '@astryxdesign/build': patch | ||
| --- | ||
|
|
||
| [fix] `astryxStylex` resolves `defineConsts` values read from another file. With the `{stylexOptions}` form (the one Storybook uses), a file that read only constants from a `.stylex.ts` module lost that import once StyleX compiled it, so the constants never reached the CSS: a constant used as a media-query key failed the build ("Invalid empty selector") and one used as a value shipped as a custom property nothing declares. In either form, a constant defined on the other side of the layer split, or in an Astryx package (compiled with the library class-name prefix) and read from an app file (compiled with its own), stayed unresolved. |
There was a problem hiding this comment.
packages/build/src/vite.ts 675 also moves a default: the {stylexOptions} form now passes treeshakeCompensation: true unless the caller sets it. spec:AST-017 FR3 asks the changeset to name the old and new default and the option that restores the old one, for example: "The {stylexOptions} form now defaults StyleX's treeshakeCompensation to true, as the zero-config form does; pass treeshakeCompensation: false to keep the previous output."
| if (options.perEventPointer === true) { | ||
| this.perEventRecognizers.add(recognizer); | ||
| } else if (!this.isCoarse) { | ||
| this.warnFinePointer(); |
There was a problem hiding this comment.
With actionGroup, this warning fires although the host serves the group where the pointer is fine (746–764, 2080–2227), and its text says the attach "stays inactive until the pointer is coarse" (1085–1087). If the option survives the spec decision, skipping the warning when options.actionGroup is set keeps the message true.
… a mouse press At the document, an event from inside an open shadow root is retargeted to the shadow host, and so is a touch's target, so the host looked for participants from the shadow host and found none inside the root: the browser then sent its own mousedown and click. Both cases, a finger and a mouse, attach a tap to an element in the shadow root. Fail until the host resolves targets through the composed path.
…composed path Inside an open shadow root the document's listeners see the shadow host, in event.target and in a touch's target alike, so a recognizer on an element in the root never got its finger, and nothing claimed it. landedOn takes the composed path's first element for the event's own target and for a touch whose target is the event's (another finger keeps its own): a touch's start, a pointer's press and its pairing, the last touch a scroll reads, a cancelled pointer's touch and hover.
…he host resolves targets through the composed path) into meta#8320
There was a problem hiding this comment.
This needs an engineering owner's decision before it can be approved: usePressTracker's perEventPointer and actionGroup attach touch work where the primary pointer is fine, which spec:AST-094 FR19 and FR24 rule out as they stand on main. An engineering owner needs to update FR19 and FR24 for the press-feedback exception (proposed in #8469) and provide the governing spec for action groups (proposed in #8622), or approve both here by commenting /approve-spec <full head SHA>, which the author may do as an engineering owner. Whatever the decision, these changes are needed:
- Match the targets
landedOnnow resolves into a shadow tree through the flat tree inonScrollandpointerFingerAt:Node.containsstops at the shadow boundary, so a flick that starts inside a web component no longer counts as the finger's scroll, and an ancestor's claim underinputMode: 'pointer-events'is lost. - Cancel a claimed finger's
touchmovewhere the host skips Touch Events (noontouchstart), and run the second-finger check before that early return. - Remove the
tracker.offsetBycall in therotationsetter, which doubles a re-based rotation's release velocity, and assert the release value in its tests. - Add the FR18–FR22 real-touch cases for an edge pan on an element wider than the band and for a still finger with sub-pixel jitter.
- Reduce the 24,707-line compare by landing the predecessors and splitting the separable pieces, name the
treeshakeCompensationdefault in the build changeset, and fix the action-group warning that says the attach stays inactive.
[Automated review]
| if ( | ||
| last != null && | ||
| time - last.time < UIKIT_PRESS.momentumMemory.value && | ||
| scroller.contains(last.target) |
There was a problem hiding this comment.
last.target is now landedOn(event), the node inside an open shadow root, and Node.contains does not look into a shadow tree: a light-DOM scroller never contains it, so a flick that starts on a web component's row no longer records the finger's scroll and isScrollBraking stays false. scroller.contains(touch.target) below (FR20's scroll route) misses the same finger. Comparing through the flat tree (parentAcrossShadow, as participantsAt does) keeps both working.
| for (const touch of moved) { | ||
| const id = | ||
| this.touchIds.get(touch.identifier) ?? | ||
| (this.inputMode === 'pointer-events' |
There was a problem hiding this comment.
Where the window has no ontouchstart, usesTouchEvents is false and onTouchStart returns before touchIds is filled, so this fallback never runs and a claimed finger's touchmove is never cancelled (spec:AST-094 FR19). Keying it on !this.usesTouchEvents covers that path; pointerFingerAt then also needs a flat-tree match for a finger that landed in a shadow root.
| if (options.perEventPointer === true) { | ||
| this.perEventRecognizers.add(recognizer); | ||
| } else if (!this.isCoarse) { | ||
| this.warnFinePointer(); |
There was a problem hiding this comment.
An actionGroup attach under a fine pointer lands here and is told it "stays inactive until the pointer is coarse" (line 1103), while refresh gives the group its pointerdown listener and serves it there (763–781).
| this.rotationValue = value; | ||
| // The velocity continues from the re-based value, without a jump: the | ||
| // samples shift with it (as the pinch's scale setter scales them). | ||
| this.tracker.offsetBy(delta, 0); |
There was a problem hiding this comment.
The tracker samples the raw unwrapped angle (lines 90 and 106), which this setter does not change, so shifting the samples by delta makes the next interval read the re-base as extra turn: with rotation = 0 set on every change, a steady 2.18 rad/s turn releases at about 4.36 rad/s (spec:AST-100 FR2). Removing this line and delta keeps the velocity continuous, as the comment at 87–89 says.
…adow root; a claimed touch's moves are cancelled where fingers come from pointer events With targets from the composed path, a finger inside an open shadow root sits below the scroller only along the flat tree, which Node.contains does not follow: a scroll under it did not take it, and its flick did not arm the scroll brake. And where the host reads fingers from pointer events (inputMode 'auto' with no ontouchstart), the blocking touchmove found no finger for a claimed pan's touch, so its moves went to the page. Three cases fail until the host compares along the flat tree and finds that finger; a fourth pins a finger scroll inside a shadow root.
… tree; a claimed touch finds its pointer finger wherever fingers come from pointer events containsAlongFlatTree walks parentAcrossShadow, so a finger inside an open shadow root, now that targets come from the composed path, counts as under a scroller outside it: a scroll there takes it, its flick arms the scroll brake, and a touch there brakes. cancelIfClaimed matches a touch to its pointer finger whenever the host reads fingers from pointer events, not only under inputMode 'pointer-events', so a claimed pan's moves are cancelled where a browser that exposes no ontouchstart still sends Touch Events.
… flat tree, and a claimed touch finds its pointer finger wherever fingers come from pointer events) into meta#8320
There was a problem hiding this comment.
This needs an engineering owner's decision before it can be approved: usePressTracker's perEventPointer and actionGroup attach touch work where the primary pointer is fine, which spec:AST-094 FR19 and FR24 rule out as they stand on main. An engineering owner either updates FR19 and FR24 for the press-feedback exception (proposed in #8469) and provides the governing spec for action groups (proposed in #8622), or approves both here by commenting /approve-spec <full head SHA>, which the author may do as an engineering owner. Whatever the decision, these changes are needed:
- Compare a finger along the flat tree in
isFingerDownInandpointerFingerAttoo: in Chromium, a finger inside an open shadow root is never read as the person's scroll, and its claimed moves are never cancelled where fingers come from pointer events. - Remove the
tracker.offsetBycall in therotationsetter, which doubles a re-based rotation's release velocity, and assert the release value in its tests. - Run the second-finger check before
onTouchStart's early return, so a one-finger pan cannot block the page's pinch where the window has noontouchstart. - Add the FR18–FR22 real-touch cases for an edge pan on an element wider than the band and for a still finger with sub-pixel jitter.
- Reduce the 24,827-line compare by landing the predecessors and splitting the separable pieces, name the
treeshakeCompensationdefault in the build changeset, and fix the action-group warning that says the attach stays inactive.
[Automated review]
|
|
||
| /** @internal Whether a finger that landed inside `node` is down now. */ | ||
| isFingerDownIn(node: Node): boolean { | ||
| return this.fingerTargets.some(target => node.contains(target)); |
There was a problem hiding this comment.
Node.contains stops at the shadow boundary, and for a tracked finger the last listener to update fingerTargets is the one watchTarget puts on the inner node, where the browser retargets the touch to that node. A light-DOM scroller (or document) then never contains it: in Chromium with Touch Events, 0 of 19 scroll events under a finger on a recognizer inside an open shadow root read as the finger's in fingerScroll.ts, against 19 of 19 in light DOM. Recording each finger's landed target (composedPath()[0]) and comparing with containsAlongFlatTree would match onScroll.
| !( | ||
| finger.target === touch.target || | ||
| finger.target.contains(touch.target) || | ||
| touch.target.contains(finger.target) | ||
| ) |
There was a problem hiding this comment.
cancelIfClaimed now reaches this wherever fingers come from pointer events, but finger.target is the node inside an open shadow root while the claiming ancestor's listener sees the touch retargeted to the shadow host, and neither contains the other. In Chromium under inputMode: 'pointer-events', a claimed pan over shadow-root content cancelled 0 of 9 moves (light DOM: 9 of 9), against spec:AST-094 FR19. containsAlongFlatTree in both directions would match it.
| if (!this.usesTouchEvents || !this.takeEvent(event)) { | ||
| return; | ||
| } | ||
| if (this.fingerTargets.length > 1) { | ||
| this.failUnclaimedLoneFingerPans(); | ||
| } |
There was a problem hiding this comment.
Where the window has no ontouchstart this returns before the second-finger check, and onPointerDown has none, so an unclaimed one-finger pan survives a second finger; now that cancelIfClaimed matches such touches to their pointer finger, that pan can begin mid-pinch and cancel its moves (spec:AST-094 FR10). Running failUnclaimedLoneFingerPans before the early return would keep the page's pinch.
| this.rotationValue = value; | ||
| // The velocity continues from the re-based value, without a jump: the | ||
| // samples shift with it (as the pinch's scale setter scales them). | ||
| this.tracker.offsetBy(delta, 0); |
There was a problem hiding this comment.
The tracker samples the raw unwrapped angle (lines 90 and 106), which this setter does not move, so shifting the samples makes each interval read the re-base as extra turn: on the test's stream (2° every 16 ms) the release is 2.182 rad/s untouched and 4.363 rad/s with rotation = 0 on each change. Deleting this line and its delta, and asserting a release near 2.18 rad/s instead of > 1.5, would keep spec:AST-100 FR2.
| if (options.perEventPointer === true) { | ||
| this.perEventRecognizers.add(recognizer); | ||
| } else if (!this.isCoarse) { | ||
| this.warnFinePointer(); |
There was a problem hiding this comment.
An actionGroup attach under a fine pointer lands here and is told it "stays inactive until the pointer is coarse" (1116–1117), while refresh gives the group its pointerdown listener and serves it there (776–794). If the option survives the spec decision, skipping the warning for an action group would keep the message true.
| '@astryxdesign/build': patch | ||
| --- | ||
|
|
||
| [fix] `astryxStylex` resolves `defineConsts` values read from another file. With the `{stylexOptions}` form (the one Storybook uses), a file that read only constants from a `.stylex.ts` module lost that import once StyleX compiled it, so the constants never reached the CSS: a constant used as a media-query key failed the build ("Invalid empty selector") and one used as a value shipped as a custom property nothing declares. In either form, a constant defined on the other side of the layer split, or in an Astryx package (compiled with the library class-name prefix) and read from an app file (compiled with its own), stayed unresolved. |
There was a problem hiding this comment.
packages/build/src/vite.ts 675 also moves a default: the {stylexOptions} form now passes treeshakeCompensation: true unless the caller sets it. spec:AST-017 FR3 asks the changeset to name the old and new default and the option that restores it (treeshakeCompensation: false).
|
Closing: carried by #8360 (this head, a8b174a, is in it by hash). The review items:
|
Depends on #8336. Review only this pull request's change: vjeux/astryx@a0c57a3...a8b174a
Why
Reviews of Astryx's gesture foundation found defects a person would hit on a phone. In Safari before 26, which sends no
scrollend, a finger's scroll never ended, so the next tap on a list counted as part of the scroll. After a long task blocked the page, a long press could begin after the finger had lifted. And WebKit read a steady drag's release at 533 px/s instead of 625, so flicks fell short.What led to it
This is part of the UIKit-parity work that makes Astryx's touch components feel as they do on iOS. The reviews of the foundation pull requests (#8058–#8061, #8071–#8074) listed these defects and a few smaller ones; this pull request collects their fixes.
Options and tradeoffs
cancel()disabling a recognizer for good, a two-finger double tap that never recognized) would wait.Why this choice
The fixes share one arena and one sampling path, so landing them together keeps them consistent, and the commits still read one defect at a time. Release velocity now comes from one tracker for drags and scrolls (
spec:AST-100FR2), and a pan claims as it begins, at UIKit's 10 pt (spec:AST-094FR17, FR19, as the owner ruled).How to check it
Evidence
spec:AST-100FR2, FR4).