Skip to content

refactor(core): BottomSheet's drag runs on the gesture recognizers - #8386

Draft
vjeux wants to merge 2267 commits into
meta:mainfrom
vjeux:uikit/c17-sheet-recognizers
Draft

vjeux wants to merge 2267 commits into
meta:mainfrom
vjeux:uikit/c17-sheet-recognizers

Conversation

@vjeux

@vjeux vjeux commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #8341, #8360 and #8665. Review only this PR's change: https://github.com/vjeux/astryx/compare/09ed788d392fb1bedaef1d136ce94cea891d8262..26f2ae686a0db229233d5262229d77e3cb5c2d07

Primary intent

Every BottomSheet drag, under a finger, a pen or a mouse, runs on the shared gesture recognizers.

User or maintainer impact

The sheet had two drag implementations that disagreed: a mouse flick closed the sheet where a finger's stops at the next detent, and a mouse dragging up from the content's end selected text. Now every pointer gets one release rule, a drag that begins after 10 px (one begun in the content stays the content's), a catch during a settle, the pull up from the content's end, and a dim that clears only once the sheet is gone. A mouse keeps the theme's timing.

Problem and solution fit

spec:AST-094 attaches gestures for every pointer. Options:

  1. Keep the mouse's old path for now: two implementations, out of step with the spec.
  2. Exempt the sheet in the spec: two implementations for good.
  3. Every pointer on the recognizers (chosen): one implementation, costing a dependency on feat(core): gestures attach for every pointer (spec:AST-094 FR43) #8665 and the mouse changes measured below.

Authority

spec:AST-094 FR13, FR14, FR17, FR19, FR36, FR43; spec:AST-017 FR59; spec:AST-107 FR7 with INV13's gaps (#8751).

Change and evidence

Finger (iOS simulator, Android emulator): detents slow pull

Mouse on main and on this PR (desktop Chrome, 1280 × 800, timed input, two runs each):

Snap points story, mouse main This PR
Handle dragged 5/10/20/60/120 px: sheet moves 0/1/5/60/120 0/0/1/50/110
Slow 150 px in 0.9 s; 300 px in 1.5 s half; half open; half
Flick 80 px in 64 ms; 200 px in 96 ms gone; gone half; half
Press during a settle settle runs on caught where it is
Drag up from the content's end, at half 42 characters selected sheet rises
Dim 80/160/240 px below half 0.50/0/0 0.80/0.59/0.40

A mouse drag, then a flick (before, after)

Scope

  • This PR has one primary intent.
  • Independently removable deltas were removed or split.
  • Public text and artifacts contain no internal Meta context.

Testing

With a mouse in the Snap points story: flick the handle down (stops at half); at half, scroll to the content's end and drag up (the sheet rises).

Core, lab, the sheet's Chromium and WebKit tests and the touchscreen-laptop test pass; 53 new tests fail on the old code. Lab's MobilePowerSearch swipe test now sends a finger's touch and pointer events on a sheet-sized box; its expectation is unchanged. touch-support-bundle fails here, not on main; #8671 replaces that check.

@vercel

vercel Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 30, 2026 3:15am UTC

Request Review

@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Oct 25, 2026
@astracat-bot

astracat-bot Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Oct 30, 2026, 4:42 PM UTC

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner: the 10 px drag threshold (a mouse's included), a second finger no longer cancelling a begun drag, and the 5 px edge claim rest on spec:AST-094 r2 DEC-2, FR10, FR14 and FR17, which are proposed in #8378 and not yet current, so an engineering owner needs to approve those claims (by landing #8378, or by commenting /approve-spec <full head SHA> here, which the author may do if they are an engineering owner) or the sheet keeps today's behaviour for them. Independently of that decision, three changes are still needed:

  • Land or rebase onto the stacked pull requests so this diff is only this PR's commit; today it is 46,769 changed lines, and the commit alone is 3,193.
  • Limit the BottomSheet.catch watcher to touch and pen (or name the change), because a mouse press anywhere on a settling sheet now stops it, which spec:AST-017 FR59 does not allow as a side effect.
  • Add mouse before-and-after evidence for the threshold and release-velocity changes, as FR59 requires for touch work that changes fine-pointer behaviour.

[Automated review]

sheetElRef,
() =>
new LongPressGestureRecognizer({
name: 'BottomSheet.catch',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This watcher takes every pointer, so a mouse press anywhere on a settling sheet now stops it until the button comes up (measured: held at 186.4 px for 500 ms after a press on the list), which main never did. spec:AST-017 FR59 keeps fine-pointer behaviour unchanged unless the PR names a defect or deliberate consistency and shows mouse evidence before and after. allowedPointerTypes: ['touch', 'pen'] here, with a mouse catch kept on the handle, would restore that.

handleElRef,
() =>
new PanGestureRecognizer({
name: 'BottomSheet.handle',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With UIKit's 10 px hysteresis this pan also delays a mouse: 8 px of handle travel no longer moves the sheet and 30 px moves it 20 px (16.9 px on main). If that is deliberate cross-device consistency, spec:AST-017 FR59 asks the PR to say so and show mouse evidence before and after; the threshold itself cites spec:AST-094 FR17, which is not current yet.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 25, 2026
@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

Preview availability: Storybook and Sandbox are not both ready on the exact-head Vercel preview. CI concluded failure.

Modified Components

MultiSelector (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 273B 273B 0B
Lines of Code 2642 2642 -
Complexity Very High Very High (344) -
Popover (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 412B 412B 0B
Lines of Code 977 977 -
Complexity Very High Very High (145) -
MobilePowerSearch (@astryxdesign/lab)
Metric Before After Delta
Entry File (ESM) 336B 336B 0B
Lines of Code 2505 2505 -
Complexity Very High Very High (319) -

Component Size

Component Import before Import after Delta CSS before CSS after Delta
core/AlertDialog 57.0KB 57.6KB +604B 12.4KB 12.4KB 0B
core/AppShell 58.0KB 58.6KB +547B 11.6KB 11.6KB 0B
core/AvatarGroupOverflow 40.1KB 40.7KB +631B 6.4KB 6.4KB 0B
core/Banner 48.0KB 48.5KB +592B 9.9KB 9.9KB 0B
core/BaseTypeahead 54.5KB 55.0KB +554B 9.9KB 9.9KB 0B
core/BottomSheet 33.9KB 69.5KB +35.6KB ⚠️ 7.9KB 8.0KB +98B
core/BottomSheetSwitcher 14.8KB 14.8KB 0B 3.3KB 3.3KB 0B
core/BreadcrumbItem 66.5KB 67.3KB +784B 11.0KB 11.0KB 0B
core/Button 43.4KB 44.0KB +602B 9.2KB 9.2KB 0B
core/Calendar 55.1KB 55.6KB +598B 10.0KB 10.0KB 0B
core/Carousel 50.4KB 51.0KB +632B 10.2KB 10.2KB 0B
core/ChatComposer 60.8KB 61.4KB +593B 10.8KB 10.8KB 0B
core/ChatComposerInput 55.9KB 56.5KB +570B 10.1KB 10.1KB 0B
core/ChatComposerTokenElement 55.9KB 56.5KB +570B 10.1KB 10.1KB 0B
core/ChatDictationButton 46.6KB 47.2KB +585B 9.5KB 9.5KB 0B
core/ChatLayout 49.0KB 49.6KB +610B 9.9KB 9.9KB 0B
core/ChatLayoutScrollButton 46.5KB 47.1KB +592B 9.6KB 9.6KB 0B
core/ChatSendButton 46.0KB 46.5KB +575B 9.2KB 9.2KB 0B
core/CheckboxInput 50.1KB 50.6KB +560B 9.3KB 9.3KB 0B
core/CheckboxListItem 54.2KB 54.8KB +546B 10.5KB 10.5KB 0B
core/ClickableCard 12.8KB 13.4KB +626B 6.9KB 6.9KB 0B
core/CodeBlock 60.7KB 61.2KB +584B 10.7KB 10.7KB 0B
core/Collapsible 11.7KB 12.3KB +608B 4.2KB 4.2KB 0B
core/CommandPalette 51.0KB 51.7KB +732B 11.8KB 11.8KB 0B
core/CommandPaletteItem 8.4KB 9.0KB +596B 3.5KB 3.5KB 0B
core/ComplexSelector 56.0KB 56.5KB +580B 10.6KB 10.6KB 0B
core/ContextMenu 74.7KB 75.6KB +866B 14.6KB 14.7KB +77B
core/ContextMenuItem 53.0KB 53.6KB +630B 9.6KB 9.6KB 0B
core/DateInput 99.7KB 135.2KB +35.5KB ⚠️ 14.8KB 14.9KB +85B
core/DateRangeInput 69.5KB 70.1KB +587B 11.2KB 11.2KB 0B
core/DateTimeInput 109.8KB 145.1KB +35.3KB ⚠️ 15.2KB 15.3KB +89B
core/DialogHeader 49.0KB 49.5KB +580B 9.8KB 9.8KB 0B
core/DrawerHeader 49.1KB 49.6KB +552B 11.2KB 11.2KB 0B
core/DropdownMenu 72.8KB 73.8KB +987B 14.7KB 14.8KB +78B
core/DropdownMenuCheckboxItem 16.1KB 16.7KB +617B 5.6KB 5.6KB 0B
core/DropdownMenuItem 15.4KB 16.0KB +611B 5.2KB 5.2KB 0B
core/DropdownMenuRadioItem 16.2KB 16.8KB +607B 5.6KB 5.6KB 0B
core/DropdownMenuSubMenu 58.2KB 58.9KB +726B 9.5KB 9.5KB 0B
core/FileInput 54.3KB 54.8KB +546B 10.3KB 10.3KB 0B
core/IconButton 43.5KB 44.1KB +602B 9.2KB 9.2KB 0B
core/Item 11.4KB 11.9KB +606B 4.7KB 4.7KB 0B
core/LayerProvider 63.4KB 64.0KB +600B 10.1KB 10.1KB 0B
core/Lightbox 51.3KB 51.9KB +565B 9.8KB 9.8KB 0B
core/Link 43.7KB 44.2KB +590B 7.6KB 7.6KB 0B
core/ListItem 53.7KB 54.2KB +554B 10.5KB 10.5KB 0B
core/Markdown 144.5KB 145.2KB +759B 17.4KB 17.4KB +85B
core/MobileNav 51.2KB 51.7KB +555B 10.1KB 10.1KB 0B
core/MobileNavToggle 46.4KB 46.9KB +572B 9.4KB 9.4KB 0B
core/MoreMenu 73.2KB 74.1KB +921B 14.7KB 14.8KB +78B
core/MultiSelector 73.1KB 73.6KB +584B 14.6KB 14.7KB +95B
core/NumberInput 55.9KB 56.4KB +598B 10.7KB 10.7KB 0B
core/Outline 28.8KB 29.3KB +589B 4.0KB 4.0KB 0B
core/Pagination 84.9KB 85.7KB +775B 15.3KB 15.4KB +90B
core/Popover 50.0KB 50.6KB +581B 12.1KB 12.2KB +87B
core/PowerSearch 158.2KB 193.4KB +35.2KB ⚠️ 19.9KB 20.0KB +101B
core/RadioListItem 52.4KB 52.9KB +571B 9.7KB 9.7KB 0B
core/SegmentedControlItem 8.6KB 9.2KB +597B 3.9KB 3.9KB 0B
core/SelectableCard 12.8KB 13.4KB +623B 7.1KB 7.1KB 0B
core/Selector 76.5KB 77.2KB +735B 15.0KB 15.1KB +92B
core/SelectorOption 14.5KB 15.1KB +601B 5.1KB 5.1KB 0B
core/SideNav 58.1KB 58.7KB +588B 10.6KB 10.6KB 0B
core/SideNavCollapseButton 46.8KB 47.3KB +573B 9.5KB 9.5KB 0B
core/SideNavHeading 54.9KB 55.4KB +564B 10.1KB 10.1KB 0B
core/SideNavItem 54.6KB 55.2KB +551B 10.1KB 10.1KB 0B
core/Step 32.6KB 33.1KB +582B 5.3KB 5.3KB 0B
core/Stepper 47.5KB 48.1KB +567B 9.6KB 9.6KB 0B
core/Switch 48.5KB 49.1KB +579B 9.3KB 9.3KB 0B
core/Tab 9.7KB 10.3KB +608B 4.1KB 4.1KB 0B
core/TabMenu 51.8KB 52.3KB +579B 10.0KB 10.0KB 0B
core/Table 83.9KB 84.7KB +790B 15.1KB 15.2KB +78B
core/TableCell 75.8KB 76.6KB +886B 14.8KB 14.9KB +78B
core/TableHeaderCell 75.5KB 76.4KB +893B 14.8KB 14.9KB +77B
core/TableSelectionToolbar 52.5KB 53.1KB +601B 11.2KB 11.2KB 0B
core/TextInput 53.7KB 54.3KB +579B 10.5KB 10.5KB 0B
core/Thumbnail 50.8KB 51.4KB +598B 11.0KB 11.0KB 0B
core/TimeInput 86.9KB 122.5KB +35.6KB ⚠️ 13.8KB 13.8KB +92B
core/Timestamp 27.7KB 27.7KB +1B 9.5KB 9.5KB 0B
core/Toast 60.0KB 60.6KB +580B 9.7KB 9.7KB 0B
core/ToggleButton 44.3KB 44.8KB +563B 9.3KB 9.3KB 0B
core/Token 29.7KB 30.3KB +600B 4.5KB 4.5KB 0B
core/Tokenizer 68.8KB 69.5KB +727B 16.5KB 16.6KB +71B
core/TopNav 52.0KB 52.5KB +545B 10.4KB 10.4KB 0B
core/TopNavHeading 54.3KB 54.8KB +557B 9.9KB 9.9KB 0B
core/TopNavItem 9.0KB 9.6KB +605B 3.8KB 3.8KB 0B
core/TopNavMegaMenu 54.3KB 54.9KB +600B 10.8KB 10.8KB 0B
core/TopNavMegaMenuItem 9.1KB 9.7KB +603B 4.1KB 4.1KB 0B
core/TopNavMenu 53.7KB 54.3KB +580B 10.1KB 10.1KB 0B
core/TreeList 32.7KB 33.2KB +581B 4.7KB 4.7KB 0B
core/Typeahead 61.6KB 62.2KB +591B 11.3KB 11.3KB 0B
lab/ChatEmojiPicker 65.2KB 65.8KB +547B 773B 773B 0B
lab/ChatReactionBar 65.8KB 66.3KB +530B 1.1KB 1.1KB 0B
lab/CommentComposer 94.2KB 94.7KB +529B 1.9KB 1.9KB 0B
lab/CommentPin 47.5KB 48.1KB +535B 1.2KB 1.2KB 0B
lab/CommentThread 216.0KB 216.8KB +776B 2.4KB 2.4KB 0B
lab/Comments 220.6KB 221.3KB +701B 2.5KB 2.5KB 0B
lab/DiffViewer 45.7KB 46.3KB +602B 2.1KB 2.1KB 0B
lab/EmojiPicker 63.1KB 63.6KB +575B 722B 722B 0B
lab/ListInput 75.3KB 75.5KB +206B 1.6KB 1.6KB 0B
lab/MobilePowerSearch 158.2KB 193.6KB +35.4KB ⚠️ 1.4KB 1.4KB 0B
lab/MobileTokenizer 91.2KB 126.7KB +35.5KB ⚠️ 775B 775B 0B
lab/Schedule 59.9KB 60.5KB +645B 3.9KB 3.9KB 0B
lab/TourStep 55.4KB 56.0KB +560B 590B 590B 0B
lab/TransferList 66.9KB 67.4KB +545B 1.7KB 1.7KB 0B
lab/TransferListSelector 72.4KB 73.0KB +599B 1.9KB 1.9KB 0B
richtext/RichTextEditor N/A 271.4KB N/A N/A 2.6KB N/A
richtext/RichTextEditorToolbar N/A 213.4KB N/A N/A 454B N/A
richtext/RichTextView N/A 251.9KB N/A N/A 2.3KB N/A

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.

⚠️ Touch-only size budget

These components grew past the touch-only budget (2 KB gzip JavaScript or 1 KB gzip CSS per component). If the growth runs only on a coarse pointer, load its JavaScript lazily so the import does not carry it, or justify the cost in the pull request description; CSS stays in the package stylesheet, so justify it.

Component JavaScript CSS
core/BottomSheet +35.6KB ⚠️ +98B
core/DateInput +35.5KB ⚠️ +85B
core/DateTimeInput +35.3KB ⚠️ +89B
core/PowerSearch +35.2KB ⚠️ +101B
core/TimeInput +35.6KB ⚠️ +92B
lab/MobilePowerSearch +35.4KB ⚠️ 0B
lab/MobileTokenizer +35.5KB ⚠️ 0B

Bundle Size Summary

Gzip figures measure each package's entry file, not the code an import pulls in.

Package Entry (ESM) Entry gzip before Entry gzip after Delta
@astryxdesign/core 4.9KB 1.2KB 1.2KB 0B
@astryxdesign/lab 4.6KB 1.9KB 1.9KB 0B
Package stylesheet Gzip before Gzip after Delta
@astryxdesign/core 34.6KB 34.7KB +94B
@astryxdesign/lab 11.1KB 11.1KB 0B

Accessibility Audit

Status: No accessibility violations detected.

Visual Regression

55 of 502 shot(s) changed.

A change here is a question, not a failure: check whether the after is the
picture you intended. Record that review on the PR. Baseline maintenance is an
explicit dispatch of CI; this report never rewrites the baseline or adds a release gate.

component story theme mode pixels
Selector Indicator Space Bottom Sheet Narrow End RTL neutral light 753,933
Selector Indicator Space Bottom Sheet Wide Start neutral light 740,072
RichTextEditor Markdown parity: side by side probe dark 558,840
RichTextEditor Markdown parity: side by side probe light 540,756
Chat Full AI Chat probe light 230,823
Chat Full AI Chat probe dark 229,805
AppShell With Mobile Nav probe light 217,010
AppShell With Mobile Nav probe dark 216,960
Collapsible Dividers probe dark 66,445
Collapsible Dividers probe light 66,445
AppShell Playground neutral dark 65,198
AppShell Playground neutral light 63,788
Chat Default neutral dark 36,304
Chat Default neutral light 35,767
Chat Empty State probe light 23,531
Chat Empty State probe dark 22,622
ChatAutoScroll Scroll Behavior Comparison neutral dark 15,378
ChatAutoScroll Scroll Behavior Comparison neutral light 14,107
Chat With Attachments probe light 13,406
Chat With Attachments probe dark 11,232

and 35 more.


Generated by PR Enrichment workflow | View full report

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner. The 10 px drag threshold (a mouse's included), a second finger no longer cancelling a drag that has begun, and the 5 px claim at a scroll edge rest on spec:AST-094 r2 FR17, FR10 and FR14, which are proposed in #8378 and not yet on main. An engineering owner needs to approve those claims, by landing #8378 or by commenting /approve-spec <full head SHA> here (the author may do this if they are an engineering owner), or the sheet keeps today's behaviour for them; note that FR14 as proposed also rules out the mid-gesture scroll hand-off this change keeps. Whatever that decision, three changes are still needed:

  • Limit the BottomSheet.catch watcher to touch and pen. A mouse press anywhere on a settling sheet now stops it until the button lifts, which spec:AST-017 FR59 does not allow as a side effect of touch work.
  • Add mouse before-and-after evidence for the threshold, or keep a mouse drag moving from its first pixel. FR59 requires this whenever touch work changes fine-pointer behaviour.
  • Land or rebase onto the stacked pull requests so this diff is only this PR's commit. The diff is 46,966 changed lines today, and the commit alone is 3,193.

[Automated review]

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
Brings the wave (FR37, FR3, FR59, the base fold) onto the migration, on
the gesture line's new head. Resolved:
- useSheetGestures.ts: the one moveDrag tracks with UIKit's rubber band
  under UIKit's presentation and today's magnet and stretch otherwise; the
  sheet's own selection lock now takes the gesture host's counted lock
  (gestures/selectionLock.ts), so the two give the sheet its values back
  in whichever order they let go; an unused spring import goes.
- useSheetGestures.test.ts: the migration's helpers for the merged tests
  (a finger's rubber band, the exact mouse magnet); the tests that touch a
  sheet right after a release let its settle report its end (a promise)
  first, so the touch finds it at rest instead of catching it.
- The Detent spec: the base's version plus the migration's pan hysteresis,
  which the rubber-band spec now also reads.
- motion/README.md: both sides' rows.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
# Conflicts:
#	packages/core/src/gestures/GestureHost.ts
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
… into meta#8386

# Conflicts:
#	packages/core/src/gestures/GestureHost.ts
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
# Conflicts:
#	packages/core/src/MultiSelector/MultiSelector.test.tsx
#	packages/core/src/Selector/Selector.test.tsx
#	packages/core/src/Selector/__tests__/Selector.combobox.a11y.test.tsx
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
…t-in, the FR41 fix and the early-claim removal) into meta#8386

# Conflicts:
#	packages/core/src/motion/uikit/index.ts
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
…ta#8386

# Conflicts:
#	packages/core/src/BottomSheet/useSheetGestures.test.ts
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
…eta#8386

# Conflicts:
#	packages/core/src/motion/uikit/index.ts
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
# Conflicts:
#	packages/core/src/BottomSheet/useSheetGestures.ts
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
…n lock under UIKit's presentation) into meta#8386

# Conflicts:
#	packages/core/src/BottomSheet/useSheetGestures.ts
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
…eta#8386

# Conflicts:
#	packages/core/src/BottomSheet/BottomSheet.tsx
#	packages/core/src/BottomSheet/useSheetGestures.ts
#	packages/core/src/motion/README.md
vjeux added 19 commits October 29, 2026 17:23
…se reads it, its last two move intervals and its lift

C17 adopted checkPacing on BottomSheet's flick detent proofs and every WebKit attempt failed, even on an idle worker: WebKit delivers a gesture's first moves with the page's own touchstart handling time (gaps of 4, 11, 3 ms against 8, 8, 8) and its lift about a frame late (8 to 11 ms against 2.1). Neither feeds a release velocity: Core's tracker reads the last three moved samples, two intervals, and a release reads as still only 40 ms after the last move. Before the check the same flicks reached the page at 2,000 px/s.

checkPacing now judges what decides a release: an event missing, either of the last two move intervals more than TOUCH_PACING_TOLERANCE_MS (4 ms) off the script, or the lift TOUCH_LIFT_STILLNESS_MS (40 ms, UIKit's measured stillness, spec:AST-100 FR2) or more after the last move. checkTouchPacing takes the played steps, so it knows which events are moves.

Red first: C17's received gaps fail the previous check and pass this one; a stall and its burst in the last two intervals still fails, and so does a lift 45 ms late, while the same stall earlier in the gesture passes. In the browser a 40 ms driver stall before the last move throws in WebKit, and Chromium plays on.
…this head

The headers of morphPath.ts and its test named rectMorph.ts and
rectMorph.test.ts, which arrive with the pull request stacked on this
one, and cited 'law M1', which no document defines. They now describe the
player without naming its file and cite spec:AST-094 FR5, which keeps
touch motion to translate, scale and opacity on the compositor.
Takes meta#8909's header fix: the paths' headers name nothing missing at that
head and cite spec:AST-094 FR5.
The header cited 'law M1' and 'laws M2, M3', which no document defines.
It now cites spec:AST-094 FR5 for compositor-only motion and FR6 for an
interruption that starts from the presented rect with its velocity, and
its paragraph wraps within the line width.
…il cancelled, and its compositor run is blocked only once on the compositor

C23 saw the control self-test fail under load at eight workers: "660 ms judged at 16.6 ms/frame: 28 frames presented, 0/28 moved". Its animations ran once, for 1,600 ms from before the screencast started, so on a loaded runner, where starting the cast takes long, the compositor's animation had ended before the block and nothing moved in the judged part.

Both animations now go back and forth until cancelled, as the pooled self-test's do, and the compositor's block uses `during`, so it starts only once that animation runs on the compositor.

Red first: with the setup stretched to 1.8 s, as a loaded runner takes, the previous test fails with exactly that read (0/39 moved) and this one passes. Normal runs at eight workers: 10 of 10 in Chromium and 10 of 10 in WebKit.
…control; the velocity proofs re-take an off-pace gesture) into meta#8109
# Conflicts:
#	packages/core/src/BottomSheet/__tests__/BottomSheetDetent.interaction.browser.spec.ts
… judges its first frame on that frame's time

'a spring started right before a block plays in full after it' waited a
fixed 300 ms of wall clock after the block, then read the screencast. In
the full interaction run on a loaded box no moving frame had reached the
screencast by then, and the proof failed on an empty read. It now waits
until the page shows the box a quarter of the way along and three more
frames reach the screencast. Its bound changes form, not strength: the
curve's own start (the handle's started) must come after the block, and
the first moving frame may be no further along than a curve started a
frame before the block ended, at that frame's own time, which is the old
30 px for a frame right after the block and still holds when a loaded
machine sends frames sparsely. A curve dated at the call is 107 px in.
Takes meta#8360's head: the held-dip proofs wait on the call's finished and a
late landing shows the element at once (88a1866), and the
spring-before-a-block proof waits for the motion (d0f5fd5). In the
animator spec the proof keeps meta#8360's waits and bounds and runs in WebKit
too, as this pull request's start-after-block makes it pass there.

# Conflicts:
#	packages/core/src/motion/__tests__/animator.interaction.browser.spec.ts
…itor run against the control, since a loaded runner's compositor skips frames

At 16 workers on a loaded box (load 92), the self-test failed 6 of 32 Chromium runs on its compositor animation. Every presented frame moved (32/32 to 38/38), but the compositor presented none for 63 to 109 ms inside the block, past the display-rate bound. Its compositor run is now judged against startBusyControl's control in the same window, with the bound's own verdict recorded beside it. The rAF mutant stays the negative and fails both: it moved on 0 or 1 of the control's frames with a stall past the control's, and the bound alone fails its longest stall. The run follows the README's order: the cast, then the animation, then the block once the animation runs on the compositor.

At 16 workers (load 115 to 164) it passed 48 of 48 in Chromium, where the control's own longest stall in the rAF runs reached 33.5 ms against the rAF's 660 ms. WebKit at 8 workers: 8 of 8.
… animations wrap instead of turning

astracat found that the control self-test's back-and-forth animation turns at 1,600 ms. When a loaded runner starts the cast late enough that the block holds the turnaround, two cast frames straddling it evenly read one position, and the control verdict fails the correct compositor animation: "moved on 6 of the 7 frames the control moved". Both self-tests' compositor animations and their timed rAF loops now wrap from 560 px back to 0, a change on every frame. The pooled case's counted mutants still bounce: they are negatives, and a turn changes their position by a full step.

BusyProof.stepPx's note now describes the step it reads from the judged part's changes, and the file header names pooledBusyProof.

Red first: with the setup stretched so the block holds the turnaround, the previous test fails 7 of 12 Chromium runs ("moved on 5 of the 6" and "6 of the 7 frames the control moved"), and this one passes 12 of 12.
…rol inside an open dialog, leaving its backdrop on the compositor

C17 found that in WebKit the control's top-layer popover, shown above an open modal dialog, takes the dialog's `::backdrop` animation off the compositor. BottomSheet's dim held its value for 155 to 225 ms into the block, then jumped to rest, while the control moved on every frame. `startBusyControl(page, {within})` now mounts the box inside the given element as a plain fixed box, with no popover and no top-layer entry. Its frames still count presented frames, and `raise()` leaves such a box where it is.

The self-test opens a modal dialog whose backdrop dims through a block, and judges the dim's brightness at a point against the control.

Red first: in WebKit at 8 workers, with the top-layer control the dim stalled in 5 of 24 runs, from 43 to 78 ms at the block's start, while the control moved ("17/20 moved, longest stall 76.5 ms at +20 ms"); with `within` it passed 24 of 24. Chromium passed either way.
…hold asks the layer, not the gestures) into meta#8203
@vjeux

vjeux commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

Thanks. On each point:

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's decision: this head adds to spec:AST-094 FR41 and its verification row that a mouse or pen drag a recognizer claims takes no new text selection and clears the page's selection once it has moved, which current FR41 does not say, so an engineering owner needs to record approval by commenting /approve-spec 26f2ae686a0db229233d5262229d77e3cb5c2d07 on this pull request (the author may do so if they are an engineering owner), or the drag keeps today's mouse and pen selection behavior. Moving BottomSheet's drag onto the gesture recognizers otherwise follows the current AST-094 drag, release and entrance rules. Whatever the decision, these still need to change before this can land:

  • Land the stacked #8341 and #8665 first (or rebase once they land) and move the @astryxdesign/build StyleX fix into its own pull request; the diff is 45,495 authored lines today.
  • Add real-device iPhone Safari and Android Chrome recordings of the slow pull from a list's top and a handle drag, as AST-094's Platform support asks.
  • Drop the detent haptic tick in useSheetGestures.ts until a UIKit measurement shows a sheet plays one there (FR23), or cite that measurement with a test.
  • Add a mouse run before and after for a press that stops a settling sheet (spec:AST-017 FR59).

[Automated review]

Comment on lines +471 to +475
(`spec:AST-017` FR59; DEC-9). A mouse or pen drag that a recognizer claims
takes no new text selection for that drag only. A claim made after the
drag has moved clears the page's selection; a press claimed at its own
pointerdown, before it moves, keeps it. An unclaimed mouse drag keeps its
released behavior.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These sentences, with the FR41 verification row at line 663, add a selection rule for claimed mouse and pen drags that current FR41 does not state: on main a mouse “otherwise keeps its released behavior”. GestureHost.ts (lockClaimedDrags) already implements it. An engineering owner records approval by commenting /approve-spec with this head's full SHA; without it, the drag keeps current FR41's mouse and pen selection behavior.

onScrimOpacityRef.current?.(dimFor(target));
if (target !== baseOffset) {
// A short tick where the platform vibrates (gestures/haptics.ts).
playHaptic('impactLight');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This vibrates 8 ms wherever navigator.vibrate exists, on every release that changes stop and under every pointer. spec:AST-094 FR23 allows a haptic only at a moment UIKit plays one, and the sheet's haptics log is still measurement task M-SH16 in motion/MEASUREMENTS.md, so nothing yet shows a UIKit sheet ticks here. Drop it until that measurement records the moment, or cite it and add FR23's haptics test.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's decision: this head carries, from #8665 and #8341, an amendment to spec:AST-094 FR41 (a mouse or pen drag a recognizer claims takes no new text selection, and clears the page's selection once it has moved) and a new shield entry in BottomSheet's element jobs, which the current records do not state, so an engineering owner needs to record approval by commenting /approve-spec 8265e441a5fa4687559c44960b125a45fc90585e on this pull request (the author may do so if they are an engineering owner), or the drag keeps today's mouse and pen selection behavior. Moving BottomSheet's drag onto the gesture recognizers otherwise follows the current AST-094 drag and release rules, and no change is needed for the detent haptic, which main already plays at the same moment. Whatever the decision, these still need to change before this can land:

  • Land #8341, #8360 and #8665 first (or rebase once they land), then split or justify the remaining 4,513-line change; the diff is 47,954 authored lines today.
  • Add real-device iPhone Safari and Android Chrome recordings of the slow pull from a list's top and of a handle drag, as AST-094's Platform support asks.

[Automated review]

Comment on lines +471 to +475
(`spec:AST-017` FR59; DEC-9). A mouse or pen drag that a recognizer claims
takes no new text selection for that drag only. A claim made after the
drag has moved clears the page's selection; a press claimed at its own
pointerdown, before it moves, keeps it. An unclaimed mouse drag keeps its
released behavior.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These sentences, with the FR41 verification row at line 663, add a selection rule for mouse and pen drags a recognizer claims that current FR41 does not state: on main a mouse “otherwise keeps its released behavior (spec:AST-017 FR59; DEC-9)”. They arrive from #8665, and GestureHost.ts lockClaimedDrags implements them. Approval waits on an engineering owner: comment /approve-spec with this head's full SHA, or keep current FR41's mouse and pen selection behavior until the amendment lands on main.

Comment on lines +233 to +237
- while a modal sheet enters under a finger, the layer's input hold adds a
shield inside the dialog: a transparent cover of the viewport that takes
a tap or a drag landing during the entrance (`spec:AST-094` FR35). It is
removed when the sheet lands. A non-modal sheet adds no shield: it holds
only input on itself, and the page beside it stays live.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds the input hold's shield to an open sheet's element jobs, which the current record does not list and the element-count test checks. It is a requirement change, so it waits on the same engineering-owner approval as the FR41 amendment.

'@astryxdesign/core': patch
---

[chore] Internal: one input hold for every layer (`Layer/inputHold`). While a layer presents, a touch, a press or a click anywhere reaches nothing (keys and assistive technology still act, and a pinch still zooms the page); while it dismisses, the same, and the layer's content is inert. The gesture host holds the layer's touches through its touch gates, and `isInputShield` lets a hit test see past a hold's shield. Not exported from the package entry point.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this head Layer/inputHold.ts registers no touch gate: the gesture host asks isInHeldLayer itself for each touch (GestureHost.ts:1561-1567). “The gesture host holds the layer's touches through its touch gates” misdescribes that; the sentence needs updating where this changeset is authored.

'@astryxdesign/core': patch
---

[fix] A BottomSheet's drag runs on the shared gesture recognizers under every pointer, a finger, a pen and a mouse: it begins after 10 px of travel and moves the sheet by the travel past that, as UIKit's does; a slow pull down from the top of the sheet's content moves the sheet instead of rubber-banding the content; and a second finger that lands during a drag no longer sends the sheet back to its detent. A mouse, like a finger, pulls the sheet down from the top of its content and up from its end while a taller detent exists, and its release dismisses the sheet or picks a detent by the same rule as a finger's. A mouse, and a pen or finger where the primary pointer is fine, keep the pointer's tracking and the theme's release timing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The iOS claim here (a slow pull from the top of the content moves the sheet) is shown only on the iOS 26.2 simulator. spec:AST-094 Platform support asks for real-device evidence for every claim about iOS Safari and Android Chrome behavior; a recording on an iPhone in Safari and an Android phone in Chrome would cover it.

This branch was successfully deployed

1 active deployment
Preview — 8265e441 Deployed Oct 30, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review needs:spec-owner-review Current knowledge records await owner approval

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants