Skip to content

fix(core): a BottomSheet touch drag's close keeps its speed, and its scrim fades with it - #8105

Merged
cixzhang merged 7 commits into
meta:mainfrom
vjeux:uikit/c17-sheet-release-wiring
Nov 6, 2026
Merged

cixzhang merged 7 commits into
meta:mainfrom
vjeux:uikit/c17-sheet-release-wiring

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Stack: #8105 → #10234 (the touch landing rule, which needs this close spring)

User impact

On a phone, a sheet a touch drag closed ran the stylesheet's close from rest instead of keeping the drag's speed, so the close stalled where the finger left it.

Problem and solution fit

  • Why: a drag's exit ran the stylesheet's close from rest (spec:AST-094 FR38), and its scrim faded on the stylesheet's own clock.
  • The fix, for touch: the close holds at the finger, then plays springs.sheetDismissSwipe (UIKit's measured swipe dismissal, now a self-registering motion role that tempo scales) from the release speed, on the shared animator.
  • The scrim: it fades for as long as that close plays (feat(core): a CSS part times itself from its spring's settle at the theme's tempo #8686's resolveSettleMs, which now times one move) and takes the close's start time, so the two end together. A BottomSheetSwitcher flow's scrim does too.
  • Mouse and pen drags, and closes no drag started, keep the stylesheet's exit; so does reduced motion (FR15).

Expected behavior and authority

component:BottomSheet FR19's close and scrim sentences; spec:AST-094 FR4, FR38, FR45 and the motion role table; spec:AST-108 FR3 and FR4a. Owner-gated.

Smallest restoration

The exit on the shared animator, one motion role, a settle time for one move, and the scrim's fade on the close's clock. No public API.

Evidence

  • Reproduction before the change (red-first arm e89319c, main plus these tests, not for landing): on main a 3,000 px/s touch close runs the stylesheet's 300 ms transition from rest at every tempo. The arm fails 6 of the 7 release and scrim-clock cases in Chromium and 15 unit cases.
  • Result after the change: the close plays 534.7, 710.0 and 1055.7 ms at tempo 0.75, 1 and 1.5, from the release speed, and its scrim fade ends with it (0.0 ms); a switcher flow's too.
  • Representative unchanged path: a mouse drag's close and a close from a control keep the stylesheet's exit.
  • Mutation-sensitive proof: without the fade's start-time write, the end-together cases fail by 17.5–18.2 ms.

Scope

  • One defect is restored.
  • Separable contradictory or unsettled tagalongs were removed or split.
  • No new public API, visual representation, default, or interaction model is hidden under the bug-fix label.
  • Public text and artifacts contain no internal Meta context.

Writing rubric results

R1–R6 pass for FR19's close and scrim sentences, its verification row and the motion role table row.

Testing

Local (Linux), at 0b72d3b. This head changes only FR19's cells and two comments, re-checked here with check:knowledge, lint, BottomSheet's units (540) and pnpm review:pre. At 0b72d3b: core tsc; units for BottomSheet, motion, hooks, SegmentedControl and Slider (2,696); CI=true pnpm lint on the changed files (0 errors); Prettier; check:knowledge; check:motion-roles (36 roles); the Changeset coverage check; pnpm review:pre (0 findings). On a Storybook built from that code: the release and scrim-clock specs 7 of 7 in Chromium (twice) and the scrim-clock case in WebKit (twice); BottomSheet's a11y specs in Chromium 71 of 71 and in WebKit 51 passed, 2 skipped; its interaction specs in Chromium 15 of 15 and in WebKit 6 passed, 9 skipped. Under a 2-core pin with 4 workers, the five end-together cases pass 100 of 100 (×20), each fade ending 0.0 ms from its close. On the red-first arm the WebKit scrim-clock case passes, because on main the close and the fade are both CSS transitions.

@vercel

vercel Bot commented Oct 24, 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 Nov 6, 2026 8:01am UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 6, 2026, 8:22 AM UTC

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approval is on hold for an engineering owner's decision: the new release behavior rests on draft spec:AST-094 (FR3–FR8, FR16–FR17), and its FR4 narrows the current architecture:theme-tokens INV3 to pointer presentations, so an owner needs to approve or amend that direction, update theme-tokens INV3 if FR4 stands, and say whether mouse drags should also take the new stop rule, which AST-094 leaves out of scope. An engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so if they are an engineering owner. Independently of that decision:

  • Land the stacked pull requests first and rebase, so this one shows only its BottomSheet change (about 2,000 changed lines instead of 25,000).
  • BottomSheet.doc.mjs and the changeset describe the new stop rule as touch-only, but handleRelease applies it to mouse drags too; say so, or keep the previous rule for fine pointers.

[Automated review]

type: 'ReadonlyArray<number | string>',
description:
"Extra heights the sheet can rest at when dragged; its own height is always the tallest stop, and omitting this gives a sheet that only opens and closes. Each stop is the sheet's visible height: a number is a viewport fraction (0.5 is half the screen), '50%' the same in CSS, '320px' an absolute length. A stop of a quarter of the sheet or less is a peek: it slides away instead of reflowing, and thins the scrim.",
"Extra heights the sheet can rest at when dragged; its own height is always the tallest stop, and omitting this gives a sheet that only opens and closes. Each stop is the sheet's visible height: a number is a viewport fraction (0.5 is half the screen), '50%' the same in CSS, '320px' an absolute length. A stop of a quarter of the sheet or less is a peek: it slides away instead of reflowing, and thins the scrim. On a touch screen a released drag settles as an iOS sheet does: on the stop nearest where its speed carries it, dismissal included, with iOS's spring.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This scopes the new stop rule to a touch screen, but handleRelease calls resolveSheetRelease for mouse drags too; only the spring and its velocity are touch-only. A mouse release at 1,000 px/s now projects 114 px instead of about 499 px, a 30 px nudge is no longer exempt, and the at-rest dismissal line moves from 40 % to 50 % of the shortest detent. Say the stop rule applies to every drag, or keep the previous rule for fine pointers.

);
// Gone: the top of the sheet at the bottom of the screen.
const dismissedOffset = maxOffset + shortestDetentHeight;
const release = resolveSheetRelease({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

resolveSheetRelease runs for every pointer, so mouse drags get the UIKit stop rule as well (0.114 s projection, off screen as a stop, halfway at rest), while draft AST-094 leaves pointer presentations out of scope and the docs and changeset describe the rule as touch-only. If mouse drags should keep the previous rule, choose the rule by releaseMotionRef.current.kind; otherwise update the docs and changeset.

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · 5518789

Storybook · Sandbox · built from this PR's head

Modified components

Component Size LoC Visual · A11y · RTL
TimeInput 145.3 kB (+1.5 kB) · 15.1 kB CSS (0) — — · — · —
BottomSheet 58.5 kB (+1.5 kB) · 7.5 kB CSS (0) — ✓ · ✓ · —
DateInput 168.3 kB (+1.5 kB) · 16.1 kB CSS (0) — — · — · —
32 more components
Component Size LoC Visual · A11y · RTL
PowerSearch 264.8 kB (+1000 B) · 22.9 kB CSS (0) — — · — · —
DateTimeInput 190.3 kB (+708 B) · 17.8 kB CSS (0) — — · — · —
Markdown 161.4 kB (+132 B) · 19.5 kB CSS (0) — — · — · —
richtext/RichTextEditorToolbar 239.6 kB (+123 B) · 454 B CSS (0) — — · — · —
Selector 88.8 kB (+120 B) · 16.5 kB CSS (0) — — · — · —
lab/Comments 281.6 kB (+116 B) · 2.5 kB CSS (0) — — · — · —
lab/TourStep 66.9 kB (+114 B) · 590 B CSS (0) — — · — · —
lab/ChatReactionBar 75.2 kB (+113 B) · 1.1 kB CSS (0) — — · — · —
DropdownMenu 85.8 kB (+110 B) · 16.6 kB CSS (0) — — · — · —
Table 99.6 kB (+110 B) · 17.2 kB CSS (0) — — · — · —
Popover 60.8 kB (+109 B) · 13.4 kB CSS (0) — — · — · —
lab/CommentThread 275.9 kB (+107 B) · 2.4 kB CSS (0) — — · — · —
lab/ChatEmojiPicker 74.7 kB (+106 B) · 773 B CSS (0) — — · — · —
MoreMenu 86.1 kB (+105 B) · 16.6 kB CSS (0) — — · — · —
Pagination 98.7 kB (+105 B) · 16.9 kB CSS (0) — — · — · —
ContextMenu 90.2 kB (+103 B) · 16.5 kB CSS (0) — — · — · —
Tokenizer 80.4 kB (+102 B) · 17.8 kB CSS (0) — — · — · —
TableCell 91.4 kB (+101 B) · 16.9 kB CSS (0) — — · — · —
TableHeaderCell 91.0 kB (+101 B) · 16.7 kB CSS (0) — — · — · —
MultiSelector 88.2 kB (+95 B) · 16.2 kB CSS (0) — — · — · —
lab/CommentComposer 115.4 kB (+72 B) · 1.9 kB CSS (0) — — · — · —
SideNav 81.5 kB (+36 B) · 10.7 kB CSS (0) — — · — · —
AppShell 81.5 kB (+34 B) · 12.8 kB CSS (0) — — · — · —
TreeList 41.5 kB (+31 B) · 5.4 kB CSS (0) — — · — · —
Collapsible 25.3 kB (+29 B) · 4.5 kB CSS (0) — — · — · —
Slider 96.8 kB (+28 B) · 8.3 kB CSS (0) — — · — · —
SegmentedControl 88.5 kB (+24 B) · 6.8 kB CSS (0) — — · — · —
BottomSheetSwitcher 17.4 kB (0) · 3.4 kB CSS (0) — ✓ · ✓ · —
ProgressBar 11.5 kB (0) · 6.2 kB CSS (0) — — · — · —
Switch 94.6 kB (0) · 9.5 kB CSS (0) — — · — · —
lab/NavigationScreen 74.9 kB (0) · 1.1 kB CSS (0) — — · — · —
lab/NavigationStack 74.9 kB (0) · 1.1 kB CSS (0) — — · — · —

Package size: @astryxdesign/core 6.69 MB packed (+6.3 kB); no new dependencies.

API changes: none in this pull request

Committed API report behind this build: cli 19 rows, core 21 rows; run pnpm api:report.

Touched components checked; add ci:run-visual or ci:run-rtl to run the full suites.


Generated by PR Enrichment workflow | View full report

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approval is on hold for an engineering owner's decision: the new release behavior rests on draft spec:AST-094 (FR3–FR8, FR16–FR17), whose FR4 narrows the current architecture:theme-tokens INV3 to pointer presentations, so an owner needs to approve or amend that direction, update theme-tokens INV3 if FR4 stands, and say whether mouse drags should also take the new stop rule and velocity reading, which AST-094 leaves out of scope. An engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so if they are an engineering owner. Independently of that decision:

  • Land the stacked pull requests first and rebase, so this one shows only its BottomSheet change (about 2,000 changed lines instead of 26,000).
  • BottomSheet.doc.mjs and the changeset describe the new release rule as touch-only, but mouse drags take it too: a slow drag released at rest between 40 % and 50 % of the sheet no longer dismisses, and a quick pull held still before release now does; say so, or keep the previous rule for fine pointers.
  • In useSheetGestures.ts, name the external system the changed reopen effect synchronizes, and stop the animator when the sheet unmounts so its completion timers do not outlive it.

[Automated review]

if (state.tracker.lastSample?.y !== event.clientY) {
state.tracker.add(0, event.clientY, event.timeStamp);
}
const velocity = state.tracker.velocity().y;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This velocity is read for every pointer, and VelocityTracker never decays through a pause, so a mouse drag held still before release keeps its last speed: on the Text only story a quick 140 px pull held 0.42 s now dismisses the sheet, where the 100 ms window read zero and kept it open. Draft AST-094 FR17 scopes this reading to touch. If mouse drags should keep the previous behavior, pick the window velocity by releaseMotionRef.current.kind; otherwise document it beside the stop rule.

setSettlingLayoutOffset(null);
gestureExitRef.current = null;
travelHandleRef.current = null;
animator.set({y: 0});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This changed Effect now also resets the animator's translate channel, but neither its comment nor its set-state-in-effect reasons name the external system it synchronizes, which architecture:react-component-runtime INV3 asks of a changed Effect. Naming the sheet's animator here, and what the state resets derive from, would cover it.

[recordSheetHeight],
[animator, recordSheetHeight],
);
useEffect(() => () => observerRef.current?.disconnect(), []);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Only the observer is released on unmount. The animator is released through sheetRef(null) → attach(null), which cancels its Web Animations but not the per-handle completion timer, so a settle cut short by unmount still resolves finished as true and calls completeScrollAreaSettle on the unmounted hook (architecture:react-component-runtime INV5). Calling animator.stop() in this cleanup settles the handles as unfinished and clears their timers.

@cixzhang

Copy link
Copy Markdown
Contributor

Posted by Cindy's agent on her behalf.

Parts of this PR re-implement behavior that already exists, or is being specified, as a shared part of core. Converging keeps one implementation per behavior:

This is about where the behavior lives, not about the PR's intent.

@vjeux

vjeux commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Agreed: one implementation.

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
@vjeux
vjeux force-pushed the uikit/c17-sheet-release-wiring branch from 558b8d6 to bd89b28 Compare October 25, 2026 00:33
@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Oct 25, 2026
@vjeux

vjeux commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Pushed in bd89b28 (head):

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approval is on hold for an engineering owner's decision: the new touch-release behavior rests on draft spec:AST-094 (FR3, FR5–FR7 and FR16–FR17), and its FR4 says touch physics are not themable while this pull request adds theme motion roles for them, so an owner needs to approve or amend that direction and choose between FR4 and the motion roles. An engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Independently of that decision:

  • Land the stacked pull requests and rebase; the BottomSheet change alone is still about 3,200 changed lines, so split it or show why it cannot be split.
  • The new platform adaptation condition is outside spec:AST-012 FR2's closed list of conditions, so remove it or have FR2 amended first.
  • AST-094 FR8 says a throw keeps its motion under Reduce Motion, which contradicts design:reduced-motion DR2 and this pull request's own code; align the record.
  • Under Reduce Motion a finger's release on a fine-pointer screen lands at once instead of cross-fading, because releaseAnimates() reads the panel's transition: none as a zero duration; let touch releases reach the animator and cover it with a test that renders BottomSheet.
  • Keep the sheet element in a ref rather than React state in useSheetGestures.ts, and describe the rule as following the primary pointer, not "under a finger", in the docs and changeset.

[Automated review]

* is everything else. Pair `ios` with `pointer: 'coarse'` for a touch
* profile, such as touch motion tuned per platform.
*/
platform?: 'ios' | 'other';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

spec:AST-012 FR2 keeps when to a closed vocabulary ("A when object may contain width, height, pointer, contrast, and motion … Future environment fields require explicit additions to this closed vocabulary"), so platform needs that record amended first. No component in this pull request reads it; removing it here, with its template, guide and changeset text, would keep the theme contract as it stands.

const gestureExitRef = useRef<SheetGestureExit | null>(null);
// The mounted sheet, as state: the dialog-level touch listeners (see
// `drawnSheetRect`) attach once it is in the DOM.
const [sheetNode, setSheetNode] = useState<HTMLElement | null>(null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

architecture:react-component-runtime INV1 says DOM nodes must not be stored in React state. The dialog listener effect only needs to know the sheet is mounted: keeping the node in sheetElRef and keying the effect on a boolean (or attaching the dialog listeners from the callback ref with a cleanup) avoids the extra render on each mount and replacement.

// theme's timing not when it is zero (transitions turned off).
const releaseAnimates = useCallback(() => {
const motion = releaseMotionRef.current;
return motion.kind === 'uikit' || motion.durationMs > 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Under Reduce Motion the panel renders transition: none, so on a fine-primary-pointer screen readReleaseMotion reads a 0 ms theme duration and this returns false. The settle is then skipped and springTravel never runs, so a finger's release (dragTouchRef) lands at once instead of the 200 ms cross-fade design:reduced-motion DR2 requires for "the release of a touch drag". Returning true for a touch release under Reduce Motion would let the animator's touchRelease fade play; the hook-only test at useSheetGestures.test.ts:2350 cannot see this because its element never gets the panel's inline transition.

cross-fades over 0.5 s on the ease-in-out curve; a sheet, a full-screen
modal or an alert fades on its own presentation spring and drops its travel
and scale; menus open and close on UIKit's Reduce Motion spring. Motion that
follows a finger, such as a throw, MUST keep its motion. **Proposed; pending

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This contradicts current design:reduced-motion DR2 (a transition started by "the release of a touch drag … MUST change opacity only and MUST finish in 200 ms or less") and DR4 (no opt-out), and the code in this pull request now cross-fades a touch release. Conforming FR8 to DR2 would keep the record and the code saying the same thing.

rest point by projecting its velocity the way its UIKit counterpart does:
with a deceleration rate, or with a measured projection time.
- **FR4 — Physics are not themable in touch presentations.** A touch
presentation MUST read its motion from the table on every theme. Themes keep

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The same diff lets themes retune these springs through motion roles (defineTheme({motion: {springs}}), resolved per element by the animator), which this sentence forbids. One of the two has to change once the owners decide whether touch physics are themable.

type: 'ReadonlyArray<number | string>',
description:
"Extra heights the sheet can rest at when dragged; its own height is always the tallest stop, and omitting this gives a sheet that only opens and closes. Each stop is the sheet's visible height: a number is a viewport fraction (0.5 is half the screen), '50%' the same in CSS, '320px' an absolute length. A stop of a quarter of the sheet or less is a peek: it slides away instead of reflowing, and thins the scrim.",
"Extra heights the sheet can rest at when dragged; its own height is always the tallest stop, and omitting this gives a sheet that only opens and closes. Each stop is the sheet's visible height: a number is a viewport fraction (0.5 is half the screen), '50%' the same in CSS, '320px' an absolute length. A stop of a quarter of the sheet or less is a peek: it slides away instead of reflowing, and thins the scrim. Under a finger, a released drag settles as an iOS sheet does: on the stop nearest where its speed carries it (a quick flick goes at least to the next stop), dismissal included, with iOS's spring; elsewhere it settles as before, on the theme's motion.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The rule is chosen by the device's primary pointer (readTouchPresentation().isTouch), not by the pointer that drags: a finger on a touchscreen laptop keeps the previous rule, and a mouse on a screen whose primary pointer stays coarse takes the iOS one. "On a screen whose primary pointer is coarse (a phone or tablet)" would describe it exactly; the changeset's "on every touch device" has the same gap.

@cixzhang

cixzhang commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 7aab77b

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changes requested: one fix remains before this can land.

  • The touch drag exit plays springs.sheetDismissSwipe as a plain table constant (BottomSheetPanel.tsx:771), but spec:AST-094 FR4 on main still requires every spring a gesture plays to be a motion role, so land #9915's FR4 change first (this then clears with no change here) or amend FR4 in this pull request with an implementation-gap note.

The touch release rule (FR19, DEC-4, the compatibility note and the FR36 sentence) is approved for this head, and the FR19 mouse note and its Basis cell are fixed. In Chromium a short fast flick now closes the Filters sheet, a flick from the top of Snap Points stops at half, and a quick mouse drag behaves as on main.

[Automated review]

const exit = animatorFor(element).animate(
{y: playedDragExit.distance},
{
spring: springs.sheetDismissSwipe,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This spring is still not a motion role: SPRING_ROLES has no sheetDismissSwipe row, and spec:AST-094 FR4 on main says every spring a gesture plays "MUST be a motion role" and a component "MUST NOT play a literal". This clears with no change here once #9915's FR4 carve-out is on main; if this pull request lands first, it needs an FR4 implementation-gap note instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed: this pull request waits for #9915, which merges first, so no change here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 10b3acb: springs.sheetDismissSwipe is now a motion role (in SPRING_ROLES, with its row in the generated motion role table), so the theme's tempo scales the close, and the scrim fades for as long as it settles. #9915 has also merged.

resolveSettleMs gives a spring role's settling time in milliseconds at
the tempo of the theme an element is in, so a CSS part that plays with
the spring starts and lands with it on one clock. A fixed role, or a
spring that is not a role, gives its own.
@cixzhang

cixzhang commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 10b3acb

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changes requested: one defect remains now that an engineering owner has approved this head's record changes.

Blocking:

  • After a touch drag closes a sheet, the scrim's fade runs on the stylesheet's 300 ms (its duration is written in the panel's exit effect after the fade has started, BottomSheetPanel.tsx:863) while the close spring the dialog waits on lasts about 700 ms, so the page looks clear but swallows taps until about 730 ms, against spec:AST-108 FR4a; decide the fade in the release callback before the scrim's opacity changes, time it from the curve the animator plays rather than the spring's settle from rest (resolveMotion.ts:155), and test the running ::backdrop transition rather than the declared duration.

The touch release rule, the FR37 cancel and the spring exit otherwise behave as the approved records describe in Chromium, and mouse and pen drags are unchanged.

[Automated review]

registerSpringsTable();
// The scrim fades for as long as the close spring takes to settle at
// the theme's tempo, so the two land together.
onScrimFadeDurationRef.current?.(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This runs when the panel enters its exit state, a commit after BottomSheet.tsx:733 set the scrim's opacity to 0. In Chromium the ::backdrop opacity transition has already started on the stylesheet's 300 ms by then, at tempo 1 and 1.5 alike, though transition-duration reads 0.531 s and 0.796 s. The value is also the spring's settle from rest (531 ms), while the exit curve the dialog waits on lasts about 700 ms. Deciding the duration in the release callback (closeSheet in useSheetGestures.ts) from the curve the animator plays would keep the scrim and the close on one clock (spec:AST-108 FR4a).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in c3d9af2. The fade is now set as the host takes the close, in a panel layout effect that runs before the host clears the scrim. It is timed from the move the animator plays: resolveSettleMs now takes the move (distance, release velocity, the animator's threshold). The release spec reads the running ::backdrop transition: at tempo 0.75, 1 and 1.5 it lasts 535, 710 and 1,056 ms, the same as the close's curve, and the dialog closes about 45 ms after it ends.

Comment thread packages/core/src/motion/uikit/sheet.ts Outdated
* How far ahead a release is projected, as seconds of its velocity.
* UIKit's sheet settles on the position nearest `offset + velocity ×
* this`, and the dismissed position (fully off screen) counts as one of
* them. So a release dismisses only when it lands nearer gone than any

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: with the flick rule, a flick from the top dismisses once its projection passes the lowest detent, so "a release dismisses only when it lands nearer gone than any detent" no longer holds. Suggested: "A slow release settles on the position nearest this landing. A flick goes to the first position at or past it."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Taken in c3d9af2.

…d turning back cancels

A touch drag's release takes UIKit's measured sheet values. It is
projected 0.1 s ahead (measured), with closed counted as one position,
and a release slower than 250 px/s is not projected (measured). A
release at 1,008 px/s or faster is a flick, as UIKit's sheet counts one.
It lands on the first position at or past its projection, and at least
on the next one. So a short fast flick at the lowest detent closes the
sheet instead of springing back. From a higher detent, a flick that
0.1 s of its speed carries past the next detent goes on: a hard flick
closes the sheet, and a softer one stops at the next detent (AST-094
FR36). A faster touch release never travels less than a slower one. A
touch release moving back toward where the drag began cancels to that
detent (AST-094 FR37). The touch release calls the shared release
policy, resolveSnapPoint, whose new flickPastProjection option lands a
flick at or past its projection.

A slow or medium touch throw now travels less than it did, and a slow
touch drag closes the sheet only past halfway from the lowest detent to
closed.

A sheet a touch drag closes holds where the finger left it, then leaves
on its close spring (springs.sheetDismissSwipe, UIKit's measured swipe
dismissal, which AST-094 FR4 and FR45 keep for a gesture's motion),
played by the shared animator from the release speed. Its scrim's fade
is decided as the host takes the close, before the scrim starts to
clear. It lasts as long as the animator plays that move at the theme's
tempo (resolveSettleMs, which now also times one move), so the two
finish together (AST-094 FR45's spring tiers, AST-108 FR4a). A reopen
during the exit takes it back. Under reduced motion the stylesheet's
exit runs.

A mouse or pen drag keeps its release: the scroll projection and the
dismiss line, with no flick, no close spring and no paced scrim.

The BottomSheet record's FR19 adds these touch values to the release
rule, with the mouse and pen differences as implementation gaps, and its
verification rows merge. AST-094 FR36's BottomSheet sentence names the
touch values. DEC-4 and its compatibility notes say so.
@cixzhang

cixzhang commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

/approve-spec c3d9af2

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changes requested: the touch release and its record changes are approved for this head, and one defect remains.

Blocking:

  • Miss: after a touch drag closes a BottomSheetSwitcher flow, its scrim fades on the stylesheet's 300 ms while the close spring plays about 700 ms, so the page looks clear but a tap 450 ms after the release is lost, against spec:AST-108 FR4a, because only the standalone sheet's panel gets onScrimFadeDuration (BottomSheet.tsx:976) and the switcher step's panel (BottomSheet.tsx:1313-1330) gets none; give the switcher step the same fade, or let the panel write it on its own dialog, and add a switcher case to the drag-exit tests.

On a standalone sheet the scrim fix works: in Chromium the running fade lasts exactly as long as the close at tempo 0.75, 1 and 1.5, and the release outcomes match the approved FR19. Optionally, tighten the Chromium scrim case from 100 ms to one frame, as FR4a's verification asks.

[Automated review]

xstyle={xstyle}
onDismiss={dismissOnLightInteraction}
onScrimOpacity={handleScrimOpacity}
onScrimFadeDuration={handleScrimFadeDuration}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Only the standalone host passes this. SwitcherBottomSheetItem's panel (around line 1326) plays the same drag exit with no fade handler, so after a touch drag closes a switcher flow the scrim clears on the stylesheet's 300 ms while the dialog stays up about 730 ms; in Chromium a tap on the page 450 ms after the release is lost (spec:AST-108 FR4a). Passing the same handler for the scrim-owning step, or having the panel write the fade on its own dialog, would cover both hosts.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 7ab8a89. The switcher step passes the same fade handler, and both hosts share one writer. A drag-closed switcher flow's scrim now fades with its 710 ms close instead of 300 ms (a new drag-exit unit case and a Chromium case cover it).

Comment on lines +554 to +556
expect(
Math.abs(fadeEnd - (atTempo.closeStart + atTempo.close)),
).toBeLessThan(100);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: spec:AST-108 FR4a's verification fails paired animations that end more than one frame apart. A 100 ms bound would pass a fade that ends six frames early; about 17 ms would guard the claim (they measure one frame apart here).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 7ab8a89: the bound is one frame (17 ms). The fade now takes the close's start time, so they end together (0.0 ms in 100 runs under a 2-core pin).

@cixzhang

cixzhang commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

/approve-spec c3d9af2

useSheetGestures.ts: main's refused-dismissal branch (meta#10141) runs in
this branch's closeSheet, for touch and for mouse or pen. A refused
request counts itself, and the scrim returns to the detent the drag
left. A taken close keeps its drag exit.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approval is on hold for an engineering owner's decision, and one defect needs a fix either way.

This head merges main. The proposed component:BottomSheet FR19, DEC-4, its verification row and compatibility note, the spec:AST-094 FR36 BottomSheet sentence and the springs.sheetDismissSwipe motion-role row are unchanged, but the earlier /approve-spec covered the previous head. An engineering owner records the decision by commenting /approve-spec <full head SHA> on this head (the author may, as an engineering owner).

Blocking:

  • Miss: after a touch drag closes a BottomSheetSwitcher flow, its dim clears in about 260 ms while the dialog stays up until about 730 ms, so a tap on the page 450 ms after the release is lost, against spec:AST-108 FR4a, because only the standalone sheet passes onScrimFadeDuration (BottomSheet.tsx:976) and the switcher step's panel (BottomSheet.tsx:1313-1330) does not; give the switcher step the same fade, or let the panel write it on its own dialog, and add a switcher case to the drag-exit tests.

Optional:

  • Regression: springs.sheetDismissSwipe is now the only spring role row not defined with springRole, so it becomes a role only when registerSpringsTable() runs (inline note).
  • The Chromium scrim case could assert one frame instead of 100 ms, as FR4a's verification asks.

[Automated review]

menuClose: MENU_CLOSE_SPRING,
subMenuOpen: SUB_MENU_OPEN_SPRING,
subMenuBack: SUB_MENU_BACK_SPRING,
sheetDismissSwipe: SHEET_DISMISS_SWIPE_SPRING,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: now that main makes each spring role row register itself on import, this is the one SPRING_ROLES row that is a plain object (SHEET_DISMISS_SWIPE_SPRING, line 246) rather than springRole('sheetDismissSwipe', ...). It becomes a role only when something calls registerSpringsTable(), which BottomSheetPanel.tsx does twice for this reason, and the doc on registerSpringsTable() now says a call "registers nothing new" and counts 18 rows. Defining the row with springRole would let both calls go.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 7ab8a89: sheetDismissSwipe is a springRole row like the others, and the panel no longer calls registerSpringsTable().

motion/uikit/index.ts: main's header and exports, with this branch's
sheet export.
@cixzhang

cixzhang commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 86761b0

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changes requested: one defect remains now that an engineering owner has approved this head's record changes.

The touch release rule, the drag exit and the springs.sheetDismissSwipe role match spec:AST-094 and the approved component:BottomSheet FR19 and DEC-4.

Blocking:

  • Miss: after a touch drag closes a BottomSheetSwitcher flow, its dim clears in about 260 ms while the dialog stays up until about 730 ms, so a tap on the page in between is lost, against spec:AST-108 FR4a, because only the standalone sheet passes onScrimFadeDuration (BottomSheet.tsx:976) and the switcher step's panel (BottomSheet.tsx:1313-1330) does not; give the switcher step the same fade, or let the panel write it on its own dialog, and add a switcher case to the drag-exit tests.

Optional:

  • Regression: springs.sheetDismissSwipe is the only spring role row not defined with springRole, so it becomes a role only when registerSpringsTable() runs.
  • The Chromium scrim case could assert one frame instead of 100 ms, as FR4a's verification asks.

[Automated review]

@cixzhang

cixzhang commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 7ab8a89

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changes requested: one scope item remains, and the earlier defects are fixed.

The touch release, the drag close and the springs.sheetDismissSwipe role match spec:AST-094 and the owner-approved component:BottomSheet FR19 and DEC-4. A drag-closed BottomSheetSwitcher flow's scrim now ends with its close.

Blocking:

  • The pull request now changes 2,073 authored lines, over the 2,000-line limit, in two parts that can land apart (the touch landing rule, about 940 lines, and the touch drag's close with its scrim clock, about 1,130), so move the close into a second pull request on top of this one and declare both with one Stack: line, or state in the description why the two cannot land apart.

Optional:

  • Two new comments in BottomSheetPanel.tsx (lines 163 and 929-931) could follow INV29's style; suggestions are inline.

[Automated review]

return (
effect?.target === dialog &&
(animation as CSSTransition).transitionProperty === 'opacity' &&
// Not a fade the drag started, which a style update replaces.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking (INV29 style): this reads as vague. Suggest:

// Skip a fade that the drag started. A style update replaces that fade.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Taken in 8bcccb5.

Comment on lines +929 to +931
// The scrim's fade follows the close. Once the close has started, the
// fade takes its start time, so the two end together. A fade that is
// not styled yet is looked for again on the next few frames.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking (INV29 style): "is looked for again" is passive. Suggest:

// The scrim's fade follows the close. Once the close starts, the fade takes
// its start time, so the two end together. If the fade has no style yet,
// look for it again on each of the next few frames.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Taken in 8bcccb5.

@vjeux

vjeux commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

On the scope item: the two parts cannot land apart. A flick that now closes the sheet leaves at its speed only on the close spring, so the landing rule alone would close more sheets on the stylesheet's close from rest, the stall this fixes. The approved FR19 and DEC-4 describe both, and the spec owners ruled one pull request (Nov 6). The description now says so, with the test share: about 1,500 of the 2,070 authored lines are tests.

A touch drag that closes a BottomSheetSwitcher flow now paces the
flow's scrim on the close's own clock, as a standalone sheet's does.
The step a drag closes is the flow's retained step, whose backdrop is
the scrim, and it takes the panel's fade duration. Both hosts share one
writer, which leaves a stack's 0s pin alone and clears only its own
value.

The scrim's fade now follows the close on one clock: once the close has
started, the running fade that clears the scrim takes the close's start
time. The close itself is unchanged. Under load a CSS transition and
the animator's close otherwise start frames apart. WebKit reports the
backdrop's fade on the dialog itself, so where no backdrop effect is
reported, the fade on the dialog is taken.

sheetDismissSwipe registers itself at import, like every other role
row, so the panel no longer calls registerSpringsTable().
@cixzhang

cixzhang commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 8bcccb5

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changes requested: one scope item remains.

The touch release, the drag close and the springs.sheetDismissSwipe role match spec:AST-094 and the owner-approved component:BottomSheet FR19 and DEC-4, and the release, drag-close and scrim unit suites pass.

Blocking:

  • The pull request changes 2,073 authored lines and no size exemption applies (the behavior-module line names useSheetGestures.ts, which is not a behavior module, and the panel and host add the close itself), while the close and its scrim clock read only dragExit and can land before the landing rule, so split them into a two-PR stack, close first and the landing rule on top, declared with one Stack: line, or show why neither order can land alone.

CI has not run on this head yet, because the pull request is a draft.

[Automated review]

…scrim fades with it

A sheet a touch drag closes holds where the finger left it, then leaves
on its close spring (springs.sheetDismissSwipe, UIKit's measured swipe
dismissal), played by the shared animator from the release speed. The
spring is a motion role that registers itself, so the theme's tempo
scales it. Its scrim fades for as long as that close plays
(resolveSettleMs now times one move), and the fade takes the close's
start time, so the two end together (AST-094 FR45, AST-108 FR4a). A
BottomSheetSwitcher flow's scrim does the same. A reopen during the exit
takes the sheet back. Under reduced motion the stylesheet's exit runs. A
mouse or pen drag keeps the stylesheet's exit.

The touch landing rule (the 0.1 s projection, the flick and the cancel),
its tests and its records move to the pull request stacked on this one.
@cixzhang

cixzhang commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 62f82d1

@vjeux

vjeux commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Split as asked: this pull request is now the touch drag's close and its scrim clock. The touch landing rule moved to #10234, stacked on this one (the close can land alone; the landing rule needs it).

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changes requested: two fixes before this can be approved.

The touch drag close on springs.sheetDismissSwipe and its scrim clock match spec:AST-094 FR38 and spec:AST-108 FR4a. The BottomSheet and motion unit suites pass.

Blocking:

  • The title and description still describe the touch landing rule this head moved out (the 0.1 s projection, the flick and the moving-back cancel, with its evidence and DEC-4), so retitle the pull request and rewrite the description for the drag close and its scrim only.
  • FR19's verification row now fails "a mouse drag that plays the close spring", but the FR19 text no longer calls the mouse and pen stylesheet exit an implementation gap, which contradicts spec:AST-094 FR38 and FR43; put the gap sentence back in FR19.

A new commit needs an engineering owner's /approve-spec for its head, and the author may comment it as an engineering owner. CI has not run on this head while the pull request is a draft.

[Automated review]

| FR17 | A BottomSheet open on the server, by its own `isOpen` or as a `BottomSheetSwitcher`'s active sheet, renders closed until hydration (no open styles, no `aria-modal`, the markup of a closed sheet) and opens in the render after hydration, the first that can enter its native host; it never claims a modality it does not have (`spec:AST-020` FR1, WCAG 2.2 SC 4.1.2). The server and hydrating renders produce the same markup (`spec:AST-013` FR9), and the open after hydration is settled, with no entry motion; later opens and closes animate as usual (`spec:AST-013` FR13). | `spec:AST-020` FR1 |
| FR18 | A finger's pull up past the tallest detent stretches the sheet on `spec:AST-094` FR3's rubber band, the same on every pull, with no hold. The sheet's surface reaches below the screen far enough for the whole stretch and the release, so the page under it never shows. A pull on the sheet's body stretches it as a pull on the grabber does, once the content has no room left to scroll that way: content that fits stretches at once, and a long list scrolls to its end first, then holds still while the sheet stretches. At every detent, a list that reaches its end mid-pull holds still while the sheet takes the rest of the pull. A sheet the caller sizes or styles keeps 48 px below the screen, and its stretch stops there. An inline block size, any `xstyle` and any class name count, because the sheet cannot read their rules. A mouse or a pen keeps its own resisted stretch, at most 48 px (`spec:AST-017` FR59). | DEC-3; `spec:AST-094` FR3 |
| FR19 | A release settles on the detent nearest its projected position (`spec:AST-094` FR36); a release whose velocity points back toward its start returns to its starting detent (`spec:AST-094` FR37). An `info` sheet whose projection passes the dismiss point closes; a `form` or `required` sheet settles on its shortest detent (FR7). Implementation gap: the settle plays `--ease-standard` over the theme's `--duration-medium`, at a speed set by the distance left rather than the finger's (`spec:AST-094` FR38). Implementation gap: in WebKit, the settle stops advancing while the main thread is blocked (`spec:AST-094` FR5). Implementation gap: a release whose velocity points back toward its start still commits when it follows a net downward drag and its projection passes the dismiss point, or when its projection lies nearer the next detent in the drag's direction (`spec:AST-094` FR37). Implementation gap: a release after 48 px of travel or less projects nothing: it is judged where the finger lifted, so a short fast flick closes an `info` sheet only when the finger itself lifts past the dismiss point (`spec:AST-094` FR36). | `spec:AST-094` FR5, FR36, FR37, FR38; FR7 |
| FR19 | A release settles on the detent nearest its projected position (`spec:AST-094` FR36); a release whose velocity points back toward its start returns to its starting detent (`spec:AST-094` FR37). An `info` sheet whose projection passes the dismiss point closes; a `form` or `required` sheet settles on its shortest detent (FR7). A sheet that a touch release closes leaves on its close spring from the release speed (`spec:AST-094` FR38), UIKit's measured swipe dismissal `springs.sheetDismissSwipe`, a motion role that the theme's `tempo` scales (`spec:AST-094` FR4, FR45). Its scrim fades for as long as that spring takes to settle at the theme's `tempo`, so the two finish together (`spec:AST-094` FR45's spring tiers; `spec:AST-108` FR4a). Under reduced motion its exit follows FR15. Implementation gap: the settle on a detent plays `--ease-standard` over the theme's `--duration-medium`, at a speed set by the distance left rather than the finger's (`spec:AST-094` FR38). Implementation gap: in WebKit, the settle stops advancing while the main thread is blocked (`spec:AST-094` FR5). Implementation gap: a release whose velocity points back toward its start still commits when it follows a net downward drag and its projection passes the dismiss point, or when its projection lies nearer the next detent in the drag's direction (`spec:AST-094` FR37). Implementation gap: a release after 48 px of travel or less projects nothing: it is judged where the finger lifted, so a short fast flick closes an `info` sheet only when the finger itself lifts past the dismiss point (`spec:AST-094` FR36). | `spec:AST-094` FR4, FR5, FR36, FR37, FR38, FR45; `spec:AST-108` FR4a; FR7 |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This row no longer says that a mouse or pen close keeps the stylesheet's exit as a gap, while the verification row (line 395) fails "a mouse drag that plays the close spring". Current spec:AST-094 FR38 has the commit start at the release velocity "under every pointer (FR43), on a spring", and FR43 says "a release from any of them settles on FR45's spring from the release velocity". The previous head carried the sentence; putting it back keeps the record and AST-094 consistent:

Implementation gap: a mouse or pen close plays the stylesheet's exit, not the close spring (`spec:AST-094` FR43, FR45).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Put back at 5518789, verbatim from the previous head, with FR43 added to the basis cell.

}
const current = dialog.style.getPropertyValue('--_sheet-scrim-duration');
if (ms == null) {
// Only its own value: a stack's 0s pin in the meantime stays.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking (INV29 style): this is a fragment. Suggest:

// Remove only the value this host wrote. A stack's 0s pin written since then stays.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Taken at 5518789, as suggested.

…s as a sentence

FR19 states again that a mouse or pen close plays the stylesheet's
exit, not the close spring, as an implementation gap against AST-094
FR43 and FR45. The scrim-fade clear's comment becomes a sentence.
@vjeux

vjeux commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

On the title and description: that review read them as they were at the 07:23:01Z push. The rename and the new description followed at 07:23:40Z. They describe only the drag close and its scrim; the landing rule's text is in #10234.

@cixzhang

cixzhang commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 5518789

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved pending CI: CI has not run on this head yet, because every test, lint and typecheck job skips while the pull request is a draft; it runs once the pull request is marked ready for review.

The touch drag close on springs.sheetDismissSwipe and its scrim clock match spec:AST-094 FR38 and FR4 and spec:AST-108 FR4a, and an engineering owner approved the component:BottomSheet FR19 changes and the motion role row for this head. The earlier requests are addressed: the title and description now cover only the drag close and its scrim, and FR19 again names the mouse and pen exit as an implementation gap. The BottomSheet and motion unit suites, core's typecheck and the new Chromium release specs pass locally on this head.

[Automated review]

This branch was successfully deployed

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

Labels

ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) ci:run-a11y Run the accessibility suite on this PR; write access, next push (AST-030 FR12) CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants