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 can't be approved yet: the row window and the UIKit wheel it builds on rest only on spec:AST-094, a draft record added in this stack, so an engineering owner has to approve or reject AST-094 (FR5 and FR9 in particular) as the picker wheel's governing spec, by commenting /approve-spec <full head SHA> on this pull request; as an engineering owner, the author can record that decision too. Three changes are needed whatever that decision is:
- Land #8162 and its predecessors first (or rebase onto a base that has them), so this pull request holds only its own 1,052-line commit instead of the 30,375-line stack.
WheelGroupkeeps its DOM element in React state (WheelGroup.tsx:88), whicharchitecture:react-component-runtimeINV1 rules out; keep the element in a ref and put only a mount flag in state.- The mouse-drag cases deleted from
DateInputTouch.test.tsxpoint toWheel.test.tsx, which has no mouse case; a primary-button drag that turns the wheel and a secondary button that does not need tests there.
The -webkit-touch-callout tweak belongs in its own change, proven by a computed-style check rather than the current source-text test.
[Automated review]
| * ``` | ||
| */ | ||
| export function WheelGroup({children}: WheelGroupProps) { | ||
| const [element, setElement] = useState<HTMLDivElement | null>(null); |
There was a problem hiding this comment.
This keeps the group's DOM node in React state, which architecture:react-component-runtime INV1 rules out ("DOM nodes and whole DOM collections MUST NOT be stored in React state; store only the minimal rendering key or own the node through a ref"). Keeping groupRef and putting only a mount key in state would conform: for example a callback ref that sets groupRef.current and flips an isMounted flag that the Wheel's placement effect depends on.
There was a problem hiding this comment.
Fixed in 8f8a358: the element stays in a ref; the group's context carries groupRef and an isMounted flag that a callback ref flips, and the Wheel's placement effect depends on it. New case "hands its element to the wheels without keeping it in React state" fails before the change; the vanishing-point-without-ResizeObserver case still passes.
| }); | ||
| }); | ||
| }); | ||
| // A finger or a mouse dragging a wheel goes through the gesture system: see |
There was a problem hiding this comment.
The six mouse-drag cases removed here are replaced by this pointer to Wheel.test.tsx, but that file has no mouse, pointer or button case. architecture:component-test-sufficiency INV3 keeps existing regression evidence until equivalent evidence subsumes it; a Wheel.test.tsx case for a primary-button mouse drag that turns the wheel, and one for a secondary button that does not, would cover it.
There was a problem hiding this comment.
At this head the six mouse-drag cases are present (a mouse gets today's column), and PickerWheel.test.tsx has primary- and secondary-button mouse cases for the touch column.
| }); | ||
|
|
||
| it('neither selects text nor opens the callout under a held finger', () => { | ||
| const source = readFileSync( |
There was a problem hiding this comment.
This reads Wheel.tsx as text, so it passes whatever the compiled column does; architecture:component-test-sufficiency INV6 does not count an assertion that repeats a source declaration. A browser check of the column's computed -webkit-touch-callout would prove the behavior, and the callout change itself fits better in its own pull request.
There was a problem hiding this comment.
Agreed, and the source check is gone at 8f8a358. The wheel's pan takes the gesture system's selection lock (preventsTextSelection, #8270, used from #8159) and the column's own user-select and callout styles are dropped. A jsdom case checks that the lock lands on the column under a held finger and lifts after (it fails without preventsTextSelection). On the iOS 26.2 simulator the column computes -webkit-user-select and -webkit-touch-callout none from touchstart to touchend (trace).
PR Analysis Report📚 Storybook Preview🧪 Sandbox PreviewModified ComponentsContextMenu (@astryxdesign/core) · View in Storybook
DateInput (@astryxdesign/core) · View in Storybook
DateTimeInput (@astryxdesign/core) · View in Storybook
MoreMenu (@astryxdesign/core) · View in Storybook
MultiSelector (@astryxdesign/core) · View in Storybook
TimeInput (@astryxdesign/core) · View in Storybook
Component 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.
|
| Component | JavaScript | CSS |
|---|---|---|
| core/DateInput | +27.7KB |
+341B |
| core/DateTimeInput | +27.9KB |
+340B |
| core/PowerSearch | +28.8KB |
+327B |
| core/TimeInput | +27.5KB |
+392B |
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 |
| Package stylesheet | Gzip before | Gzip after | Delta |
|---|---|---|---|
| @astryxdesign/core | 33.7KB | 34.0KB | +291B |
Accessibility Audit
Status: No accessibility violations detected.
Visual Regression
48 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 | 556,900 |
| RichTextEditor | Markdown parity: side by side | probe | light | 538,816 |
| 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 28 more.
Generated by PR Enrichment workflow | Storybook | Sandbox | View full report
db48788 to
93a47dd
Compare
There was a problem hiding this comment.
Approval is on hold until an engineering owner decides whether draft spec:AST-094 (in particular FR5 and FR9, which the wheel's row window rests on) should govern the touch picker wheel, and provides or identifies the governing spec for the new role-keyed theme motion API and the package-level experimental subpaths. An engineering owner records the decision on the draft by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Whatever that decision, these fixes are needed:
- The 744 px compact edge in
packages/core/src/utils/touchPresentation.tsand the newplatformadaptation condition contradictspec:AST-012FR9 and FR2 onmain. - The DateTimeInput time sheet loses its 8 px column gutter on Android and with a mouse, although its changesets say Android and mouse sheets and wheels are unchanged.
WheelGroupstores its DOM node in React state, and the callout test inPickerWheel.test.tsxonly reads source text.- At about 34,800 authored lines, the stack should land as separate pull requests along its seams.
[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.
This private 744 px edge contradicts spec:AST-012 FR9 on main: compact is a width below the theme’s effective md point (768 by default) or a height below 500, and no resolver keeps size numbers of its own. As written, an iPad mini in portrait (744 × 1133) gets the popover where FR9 requires the sheet.
| * 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 when vocabulary to width, height, pointer, contrast and motion, and says future fields need an explicit addition to it. platform needs that amendment first, or should come out of this stack.
There was a problem hiding this comment.
Not in this pull request's commits: themeAdaptations.ts and its platform field come from #8076's stack. The FR2 amendment or the removal belongs to that change.
| }, | ||
| touchTimeWheels: { | ||
| display: 'flex', | ||
| alignItems: 'center', |
There was a problem hiding this comment.
Dropping gap: spacingVars['--spacing-2'] here also changes the non-iOS sheet, where WheelGroup renders a bare fragment: in Chromium with a coarse pointer the hour, minute and AM/PM columns lose their 8 px gutter and the band capsules touch. wheel-group-inputs says Android and mouse sheets are unchanged, and wheel-uikit-picker says the same of the wheels.
There was a problem hiding this comment.
Right: #8161 had dropped the gutter, and under a fine pointer WheelGroup renders its columns as a fragment, so today's columns touched. Fixed in ee5538b (re-picked into this branch). The time row's style is main's again, and a new Chromium check under a mouse requires every gap between adjacent columns to be at least 8 px. It read 0 px on the previous head.
| * ``` | ||
| */ | ||
| export function WheelGroup({children}: WheelGroupProps) { | ||
| const [element, setElement] = useState<HTMLDivElement | null>(null); |
There was a problem hiding this comment.
architecture:react-component-runtime INV1: DOM nodes must not be stored in React state. Owning the element through a ref and publishing the ref (or a stable getter) through context avoids the extra render that publishing the node causes.
There was a problem hiding this comment.
Fixed at 8f8a358: the element stays in a ref. The group's context carries the ref and a mount flag, so each column places its vanishing point once the group mounts. The case "hands its element to the wheels without keeping it in React state" fails before the change.
93a47dd to
8f8a358
Compare
8f8a358 to
2d5b2a0
Compare
There was a problem hiding this comment.
Approval is on hold until an engineering owner decides whether draft spec:AST-094 should govern the touch picker wheel (in particular FR5 and FR9, which the row window rests on, and FR4, which forbids the themable touch motion this stack adds), and provides or identifies the governing spec for the theme motion roles, the package-level experimental subpaths, the nearest-valid rest, the press-feedback selection lock and the new astryxStylex option. An engineering owner records the decision on the draft by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Whatever that decision, these fixes are needed:
- The
platformadaptation condition contradictsspec:AST-012FR2. - The compact edge reads the default
mdpoint rather than the active theme's, againstspec:AST-012FR9 and thetouch-presentationchangeset. defineThemesilently accepts motion role ids that no table registers, including thetiming.reducedMotionCrossFadeMsexample in the changeset.- At about 36,200 authored lines, the stack should land as separate pull requests along its seams.
[Automated review]
| export const COMPACT_QUERY = DEFAULT_COMPACT_DEFINITION.query; | ||
|
|
||
| /** Both: presentations such as a sheet in place of a popover. */ | ||
| export const COMPACT_TOUCH_QUERY = | ||
| DEFAULT_COMPACT_DEFINITION.coarsePointerQuery; |
There was a problem hiding this comment.
spec:AST-012 FR9 puts the edge at the effective md point and lists an overridden md among its verification states, but DEFAULT_COMPACT_DEFINITION keeps 768 px under a theme that sets adaptations.widthBreakpoints.md, while the touch-presentation changeset promises the theme's point. Resolving compactDefinition() from the active theme's breakpoints, as AppShell reads them through useThemeDefinition(), would conform.
There was a problem hiding this comment.
This file comes from #8076, which this pull request carries because it is stacked on it; the change belongs there and reaches this pull request when its base does.
| const registered = motionRole(id); | ||
| if (registered !== undefined && registered.kind !== kind) { | ||
| throw new Error( | ||
| `${location}: "${id}" is a ${registered.kind}; set it under the matching group.`, | ||
| ); | ||
| } |
There was a problem hiding this comment.
An id that no table registers falls through this check and writes a --motion-* variable nothing reads: timing.reducedMotionCrossFadeMs (excluded at motion/uikit/timing.ts:158, yet the example in touch-motion-theme-roles) and a typo such as springs.sheetPresnt both pass without an error. Rejecting ids outside the generated role list would surface both.
There was a problem hiding this comment.
This file comes from #8257, which this pull request carries because it is stacked on it; the change belongs there and reaches this pull request when its base does.
2d5b2a0 to
c21f16b
Compare
Her arena work (the touch depth, a view's delivery only to involved recognizers, the reset count, touchBeganElsewhere's delivery, the simultaneity rule) reaches the recognizer's plumbing through the internal view, as this PR keeps it out of the public type; the new deliverBeganElsewhere joins that view.
Her foundation fixes for the review items this PR also answered take her form: the swipe's segment reading with directions ruled out for good, the tap's lift centroid, the claim release that counts a recognized discrete gesture, Hover's restart and the idle recognizer's comment. Both sides' tests stay: her spacing, 40-degree, re-basing and settling cases beside this PR's 20-degree, re-based release and mid-hover cases.
…ades On a touch screen, or after a touch drag, the wheel's own scrolls cross-fade with the rows landing while they are faded out (design:reduced-motion DR2); otherwise they land at once (DR3). "With a mouse or keyboard" read as if a key step on a touch screen landed at once.
… they add picker.tapSpin.durationMs is a role defineTheme accepts and astryx docs motion lists, a compatible capability, so the entry is a [feat] that names it first (spec:AST-017 FR7) instead of an internal [chore].
…urves.move spec:AST-094 FR4 names the table's roles by what they do: the base now calls Core Animation's ease-in-ease-out timing.curves.move (the same control points, 0.42, 0, 0.58, 1). The tap spin's default curve and the picker table's notes read that role. With the old name the tap spin baked an undefined curve: at the merge, wheelPhysics.test.ts's tap spin and three of wheelDrum.test.ts's looping cases failed.
…e base The base's motion docs topic and this branch's picker role (the tap spin's duration, picker.tapSpin.durationMs) each moved the topic's skeleton; the merge kept the base's golden. Regenerated with `vitest run packages/cli/test/experimental-isolation.test.mjs --update`: only the motion topic's skeleton hash changes, and no experimental surface is named.
…gnizer and compositor curves The picker wheel is a native scroll-snap column. WebKit gives every scroll-snapping scroller the fast deceleration rate (0.99/ms) while UIPickerView decelerates at the normal rate (0.998) onto a row it picks from its own projection, and a native scroll's momentum cannot be caught or held to a curve. Under a finger, on every touch device (one touch model, iOS and Android alike), the Wheel is now UIPickerView's, PickerWheel.tsx, on the gesture system and the picker's values in the UIKit constants table: - A ScrollViewPanGestureRecognizer, the pan a scroll view uses, drags the rows, on UIKit's rubber band past an end. The lift plays UIKit's release plan (wheelPhysics.ts) from a scroll view's release velocity (the pan's last two, blended as UIPickerView's); a finger landing on a moving wheel stops it where it is; a tap turns a row onto the band. Hidden mid-drag, the wheel drops the drag uncommitted and parks the committed row when shown again. - Each motion plays through wheelMotion.ts: per-row Web Animations on the compositor, each row turned onto the cylinder, with a selection tick per row reached where the browser can vibrate. - The tap spin's duration and curve and the bounce at an end are the theme's motion roles (picker.tapSpin.durationMs, timing.curves.easeInOut, scroll.bounce), resolved against the column as each motion starts. - The value commits when the wheel comes to rest; keys and clicks commit at once and turn the wheel. Home and End go to the first and last enabled row wherever the wheel is heading. The listbox's active descendant is the committed row, derived during render. - Under reduced motion (design:reduced-motion) every transition the wheel starts lands without travel: the rows cross-fade onto their row on the animator's Reduce Motion fade (reducedMotionFade, WheelMotion.crossFade), whatever curve it was asked for; the drag itself follows the finger. - isLooping, resolveRest and a spinTo(index) handle for the date and time wheels. - WheelGroup: one band and one vanishing point across picker columns. A Wheel on its own draws its own band. The band is decoration, so the frame's (or the lone column's) ::before draws it, marked data-wheel-band (INV7). The vanishing point follows the shared resize observer. - A held finger never starts text selection or the callout: the pan asks the gesture host for it (preventsTextSelection). The column keeps the pointer of a finger that lands on it, as it keeps the touch: a BottomSheet would otherwise arm its pull-down on the pointer and follow the first 8 px of a downward drag. The switch reads useTouchPresentation. A wheel shows only on a sheet, so a caller who pins the fine surface gets no sheet and no wheel (architecture:public-component-api INV16). Under a mouse or other fine pointer Wheel keeps today's scrolling column, now named ScrollWheel and otherwise unchanged (the released DateInput tokens stay as they are; the touch geometry is in the unexported wheelGeometry.stylex.ts); there spinTo commits at once, isLooping and resolveRest do nothing, and WheelGroup renders its children alone. PickerWheel.tsx is the touch layer, imported by Wheel.tsx alone. The shared types and nearestEnabledIndex move to wheelOptions.ts.
…s coarse The gesture host takes input only where the primary pointer is coarse (spec:AST-094 FR19, the gesture line's coarse-pointer gate), and the touch column draws only there. PickerWheel.test.tsx ran under jsdom's fine pointer, so on that base every touch case saw no input: 11 of 30 failed. Each case now runs under a coarse primary pointer, as on a phone or a tablet; the mouse cases are a mouse on a touch device, a tablet's trackpad.
The base renamed Core Animation's ease-in-ease-out to timing.curves.move (spec:AST-094 FR4, the same control points). The tap spin resolves that role under the column's theme, and the docs and the release note name it. With the old name the spin resolved an undefined curve, and 13 of PickerWheel.test.tsx's cases that turn the wheel failed at the merge.
…nLock The base replaced a recognizer's preventsTextSelection with the selectionLock option (spec:AST-094 FR41): a recognizer whose hold does something passes selectionLock: lockSelection from gestures/selectionLock.ts. The wheel's pan is such a recognizer (a held finger turns the wheel), so it keeps the lock.
… one picker under a finger The month and year wheels of the DateInput sheet and the time wheels of the TimeInput and DateTimeInput sheets sit in a WheelGroup. Under a finger, on every touch device, one selection band spans every column, and each column's dimmed rows lean toward the picker's centre, as UIPickerView draws them. Under a mouse or other fine pointer the group renders the columns alone, so the sheets keep today's layout; the sheets' own styles are unchanged (the DateTimeInput time row keeps its gutter between today's columns: a Chromium check under a mouse holds every gap to 8 px or more).
Wheel.interaction.browser.spec.ts drives the TimeInput sheet's minute wheel with real touch input; a finger gets the touch column on every engine. In Chromium and WebKit: a flick coasts on UIKit's curve (the release plan of wheelPhysics.ts, fitted to the engine's own release velocity) onto the row it projects and commits on arrival; a finger landing on the coasting wheel stops it where it is, then it eases onto the nearest row; and a storyboard films a flick and its coast, its trace asserted. In Chromium, which replays the script's timestamps: the coast keeps advancing every frame through a main-thread block, and does no Layout or Paint per frame. WebKit's real-time input gives the release its own velocity, so the iOS simulator capture proves the block there. Under a mouse (Chromium, a desktop profile) a drag scrolls today's column onto a row and commits it. Under reduced motion (design:reduced-motion) a flick cross-fades onto its row: every animation under the column, sampled each frame, animates opacity alone for 200 ms or less (Chromium, WebKit). PickerWheel.test.tsx adds a jsdom case for a path the audit named: each column leans toward the picker centre from where it is laid out, so an RTL row mirrors the lean.
Under a finger, opening a DateInput or DateTimeInput bottom sheet, and its month and year wheels, held the main thread longer for each year in the range: every wheel drew every row twice (on the cylinder, each its own 3D layer, and on the band's copy). In Mobile Safari (iOS 26.2 simulator), opening the year wheel of a 200-year range held the page for 278-754 ms; it now takes 54-65 ms. UIPickerView lays out only the cells it shows. PickerWheel now draws a window of rows: on the cylinder its front half (rows within 90 degrees of the band), and on the band's copy the row under the band and one each side, each with a buffer of 2: 15 and 7 rows at most at rest, for any list. Every motion mounts the rows it will show before it plays (flushSync), so its cost follows the distance it travels; a reduced-motion cross-fade mounts its target's rows at the fade's midpoint; a drag moves the window when the finger nears its edge; the window settles back around the rest. The band copy's rows sit at their positions along the wheel, so a looping wheel needs no copies of its strip. WheelMotion attaches rows by index and carries a running motion through a re-attach: rows that mount join it in step, rows that unmount leave it, and rows that mount during a cross-fade show where it is. The listbox's options are separate, plain and complete, stacked transparent over the band: assistive technology meets every option, and both drawings carry their text as generated content. Also: WheelGroup flags when its element is attached (the element stays in a ref), so a Wheel places its vanishing point at the group's centre once the group mounts; and a test holds the gesture host's selection lock on the column under a held finger. Stories BottomSheetWideRange (200 years) and BottomSheetNarrowRange (2 years) back the browser proof. Under a mouse, its year wheel stays today's column with every year listed. Wheel.interaction.browser.spec.ts marks the row drawn on the cylinder for its busy-main-thread proof (the option it marked is transparent now).
e068810 to
9a52dc9
Compare
|
Folded into #8162, so this pull request closes. Today's look (#8159) draws each option once and leaves the rows out of sight upright and transparent, so the row window has nothing left to do. Its activation proof and the two stories moved to #8162, where opening the year wheel at a phone's CPU (Chromium, 4× throttling) measures no slower than today's column: 167 ms worst frame for 200 years against 200 ms under a mouse, and 133 ms for both at two years. |
Depends on #8162 (and its predecessors); review the last commit.
User impact
Under a finger, opening a DateInput or DateTimeInput sheet, or its month and year wheels, freezes the page longer for each year in the range; the sheet's motion skips. #8209 surfaced it.
Evidence
iOS 26.2 simulator,
BottomSheetWideRange(200 years), worst frame gap after each tap, 2 runs:On #8209's story: 175–238 ms with 101 years, 58–59 ms with one. Cutting the looping copies changed nothing; the cost is rows.
Across the window's edge, each row is drawn before it shows (a blue dot marks the finger, drawn for the recording):
Android 15 emulator, Chromium 158, 200 years:
Problem and solution fit
UIPickerView lays out only the cells it shows.
PickerWheelnow draws the cylinder's rows within 90° of the band and the band copy's three rows, each with 2 more on each side: at most 15 and 7, for any list. Motions mount their rows before playing (a reduced-motion cross-fade, at its midpoint); drags move the window near its edge; it settles back at rest. The band copy's rows sit at their positions, so looping needs no strip copies. The listbox keeps every option, stacked transparent over the band, for assistive technology. Today's column, under a fine pointer, is unchanged.Also: WheelGroup keeps its element in a ref and flags its mount, so each column places its vanishing point once the group mounts.
Expected behavior and authority
spec:AST-094(#8378) FR5; UIPickerView's cell reuse.Smallest restoration
The window lives in
PickerWheel.tsx, row attachment inwheelMotion.ts, the strip's position inwheelDrum.ts; no API change. Stories with 200 and 2 years back a browser proof. #8162's busy proof now marks the drawn row.Scope
Testing
PickerWheel.test.tsx: the window (15 cylinder and 7 band rows of 200, every option listed), the ends, a fling drawing its path then settling, a drag moving the window, a looping window wrapping, keys past the window, the vanishing point once the group mounts, and the selection lock under a held finger (fails withoutpreventsTextSelection). The window cases fail on test(Wheel): the picker wheel's browser proofs, storyboard and activation #8162.WheelActivation.interaction.browser.spec.ts: under a finger, rows on the cylinder are 2 of 2 and at most 15 of 200 (200 on test(Wheel): the picker wheel's browser proofs, storyboard and activation #8162: red), and at 4× CPU throttle the 200-year opening stays within the 2-year one; under a mouse the year wheel is today's column with every year. On the stack top's build, Chromium 2 passed; WebKit 1 passed, 1 skipped (mouse).Wheel.interaction.browser.spec.ts, Chromium 7 passed and WebKit 4 passed, 3 skipped; the rest and pinch-zoom specs, 11 passed.pnpm exec vitest run packages/core/src/DateInput packages/core/src/TimeInput packages/core/src/DateTimeInput packages/core/src/motion packages/core/src/gestures packages/core/src/utils packages/core/src/BottomSheet: 95 files, 2320 tests.pnpm -F @astryxdesign/core typecheck, ESLint and Prettier on touched files,pnpm check:changesets.Stack (merge in this order): … → #8156 → #8159 → #8161 → #8162 → this PR (the full order is in #8156).