Repository navigation
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
b90033e to
168ae59
Compare
There was a problem hiding this comment.
This can't be approved yet. Against main it ships the whole unmerged touch-presentation stack, and that stack is governed by the draft spec AST-094 (touch presentations follow UIKit), which no owner has approved at this head and which proposes changes to current theme-token, user-state, button, interaction-modality and scroll-area rules. An engineering owner needs to approve, amend or reject that record, including how its List reorder fits the draft ordered-collection-reordering design, by commenting /approve-spec <full head SHA> on this pull request; the author can do this as an engineering owner. Independently of that decision, these need fixing:
- The reorder handle needs a single-pointer way to move a row without dragging (WCAG 2.2 SC 2.5.7).
useListUpdateMotionreads every row's layout after each commit even whenhasAnimatedChangesis off.- The
lintjob runs out of JavaScript heap on this head. - Landing the six predecessor pull requests first makes this diff the single test file the description describes, instead of about 32,000 lines.
[Automated review]
| }, [ref]); | ||
| // A finger claims after the content-touch delay, if it stayed still and | ||
| // is the only finger on the screen. | ||
| useGestureRecognizer( |
There was a problem hiding this comment.
The handle's only pointer paths are these two long-press recognizers (hold then drag on touch, press-and-drag with a mouse), and the button has no click behavior. Someone who cannot drag or hold still therefore has no way to move a row with a single pointer (WCAG 2.2 SC 2.5.7, Dragging Movements). A tap path that reuses the keyboard moves, such as a Move up, Move down, Move to top and Move to bottom menu, would close it.
| const places = new WeakMap<HTMLElement, number>(); | ||
| for (const row of rows) { | ||
| // Inside the list: content above it changing size moves no row. | ||
| const base = row.offsetParent === element ? 0 : element.offsetTop; |
There was a problem hiding this comment.
This effect runs after every commit of every List and reads offsetParent, offsetTop and offsetHeight before it checks animates at line 97, so a List with hasAnimatedChanges off (the default) still forces a synchronous layout of all its rows on each re-render. Returning before the reads when motion is off, and clearing placesRef so the first enabled commit records a baseline, avoids that.
168ae59 to
dae4836
Compare
PR Analysis Report
View Storybook for this PR · View Sandbox for this PR Generated by PR Enrichment workflow | View current CI run |
There was a problem hiding this comment.
This can't be approved yet: against main it carries the whole unmerged touch-presentation stack, which is governed by the draft spec AST-094 (touch presentations follow UIKit), and no engineering owner has approved that record at this head, including its proposed changes to the current theme-token, user-state, button, interaction-modality and scroll-chaining rules. An engineering owner needs to approve, amend or reject AST-094, including how its List reorder fits the draft ordered-collection-reordering design, by commenting /approve-spec <full head SHA> on this pull request; the author can do this as an engineering owner. Independently of that decision, these need fixing:
- The reorder handle needs a single-pointer way to move a row without dragging (WCAG 2.2 SC 2.5.7).
- The
lintjob runs out of JavaScript heap on this head, while it passes onmain. useListUpdateMotionreads every row's layout after each commit even whenhasAnimatedChangesis off.- Landing the six predecessor pull requests first makes this diff the single 837-line test file the description describes, instead of about 32,000 lines.
[Automated review]
| useGestureRecognizer( | ||
| ref, | ||
| () => | ||
| new LongPressGestureRecognizer({ | ||
| name: 'ListReorderHandle.touch', | ||
| minimumPressDuration: reorder.claim.delayMs, | ||
| allowableMovement: reorder.claim.allowableMovement, | ||
| allowedPointerTypes: ['touch', 'pen'], | ||
| shouldBegin: () => fingersDown <= 1, | ||
| }), | ||
| onGesture, | ||
| {manageStyle: false, isEnabled}, | ||
| ); | ||
| // A mouse drags at once. | ||
| useGestureRecognizer( | ||
| ref, | ||
| () => | ||
| new LongPressGestureRecognizer({ | ||
| name: 'ListReorderHandle.mouse', | ||
| minimumPressDuration: 0, | ||
| allowableMovement: Number.POSITIVE_INFINITY, | ||
| allowedPointerTypes: ['mouse'], | ||
| }), | ||
| onGesture, | ||
| {manageStyle: false, isEnabled}, | ||
| ); |
There was a problem hiding this comment.
These two long-press recognizers are the handle's only pointer paths: hold still then drag on touch, press and drag with a mouse. The button has no click behaviour, so a tap or a screen reader's double tap starts nothing, and people who cannot drag (or a touch screen-reader user, who has no arrow keys) cannot reorder at all. WCAG 2.2 SC 2.5.7 (AA, part of the baseline in spec:AST-020 FR1) asks for a single-pointer path without dragging; for example, activating the handle could open Move up, Move down, Move to top and Move to bottom, reusing keyDown's moves.
| useIsomorphicLayoutEffect(() => { | ||
| const element = listRef.current; | ||
| if (element == null) { | ||
| return; | ||
| } | ||
| const rows = rowsOf(element); | ||
| const places = new WeakMap<HTMLElement, number>(); | ||
| for (const row of rows) { | ||
| // Inside the list: content above it changing size moves no row. | ||
| const base = row.offsetParent === element ? 0 : element.offsetTop; | ||
| places.set(row, row.offsetTop - base); | ||
| } | ||
| const before = placesRef.current; | ||
| placesRef.current = places; | ||
| const height = element.offsetHeight; | ||
| const grew = height - heightRef.current; | ||
| heightRef.current = height; | ||
| // The first commit, motion off, or a reorder session that owns the rows: | ||
| // remember where rows are, move nothing. | ||
| if ( | ||
| before == null || | ||
| !animates || | ||
| element.hasAttribute('data-astryx-reordering') | ||
| ) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
This layout effect runs after every commit of every List and reads each row's offsetParent/offsetTop and the list's offsetHeight before it checks animates, so a List without hasAnimatedChanges (and every List under a mouse) forces a synchronous layout on each re-render. Returning before the reads when motion is off, and resetting placesRef so the first enabled commit records a baseline, keeps the default path free (architecture:react-component-runtime INV5: optional behaviour acquires resources only while enabled).
| export interface HoldSpringTiming extends StartTime { | ||
| readonly spring: SpringDescription; | ||
| readonly velocity?: number; | ||
| } | ||
|
|
||
| /** A fixed duration on a Bézier curve (Reduce Motion's cross-fade). */ | ||
| export interface HoldCurveTiming extends StartTime { | ||
| readonly durationMs: number; | ||
| readonly curve: BezierPoints; | ||
| } |
There was a problem hiding this comment.
HoldSpringTiming and HoldCurveTiming are exported from @astryxdesign/core/utils, but they require SpringDescription and BezierPoints from the internal motion modules, which no entry point exports, so a caller of animate(to, timing) cannot name the spring or curve it passes (architecture:public-component-api INV1). Exporting those two types beside holdSelection, or narrowing the public timing, closes the gap.
| this.announce( | ||
| this.messages?.dropped(slot + 1, rows.length) ?? '', | ||
| 'polite', | ||
| ); |
There was a problem hiding this comment.
This announces "Dropped, position {slot + 1} of {total}" before onReorder runs, and if the consumer declines the move (a path the docs support) refuse() springs the rows back without announcing anything, so a screen-reader user hears a move that did not happen. The key path has the same shape at the position announcement after onReorder. Announcing from commit() once the new order is in the DOM, and announcing the cancellation when a declined drop springs back, would keep the message true.
dae4836 to
5523cb7
Compare
There was a problem hiding this comment.
This can't be approved yet: against main it carries the whole unmerged touch-presentation stack, which is governed by the draft spec AST-094 (touch presentations follow UIKit), and no engineering owner has approved that record at this head, including its proposed changes to the current theme-token, user-state, button, interaction-modality and scroll-chaining rules and how its List reorder fits the draft ordered-collection-reordering design (stable neighbours until the drop, an insertion cue shared with the keyboard, and no travel under reduced motion). An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so as an engineering owner. Independently of that decision, these need fixing:
- The reorder handle needs a single-pointer way to move a row without dragging (WCAG 2.2 SC 2.5.7).
useListUpdateMotionreads every row's layout after each commit even whenhasAnimatedChangesis off.- A move that
onReorderdeclines is still announced to screen readers as made. - Landing the six predecessor pull requests first makes this diff the single 989-line test file the description describes, instead of about 32,000 lines.
[Automated review]
|
On the predecessor stack: this pull request is stacked, as its first line says, and only its last commit (5523cb7, the 989-line spec) is its own. It rebases onto main as each predecessor lands, and the diff then shrinks to that file. On |
5523cb7 to
4d993ba
Compare
4d993ba to
10b416e
Compare
There was a problem hiding this comment.
This can't be approved yet: against main it carries the whole unmerged touch-presentation stack, which is governed by the draft spec AST-094 (touch presentations follow UIKit), and no engineering owner has approved that record at this head, including its proposed changes to the current theme-token, user-state, button, interaction-modality and scroll-chaining rules, whether themes may retune touch motion (its FR4 says they may not, while the stack lets them), and how its List reorder fits the draft ordered-collection-reordering design. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so as an engineering owner. Independently of that decision, these need fixing:
- Two unit tests the stack adds fail at this head, in
useListUpdateMotion.test.tsxanduseTreeExpansionMotion.test.tsx. - Touch-only motion ships statically in every
TreeListandListimport (about 15 KB gzip for the TreeList expansion alone) without lazy loading or a reported, justified size. - AST-094 FR8's 0.5 s Reduce Motion cross-fade contradicts the current 200 ms reduced-motion rule that the code already follows.
@astryxdesign/core/motion/reducedMotion.stylexwould ship as unmarked stable API that no changeset names.- Landing the predecessor pull requests first makes this diff the single test commit the description describes, instead of about 45,000 lines.
[Automated review]
| if (menu !== 'mounting') { | ||
| return; | ||
| } | ||
| const frame = requestAnimationFrame(() => setMenu('open')); |
There was a problem hiding this comment.
This opens the moves menu from an Effect one frame after a render that shows it closed ('mounting', aria-expanded="false"), and the comment names no external system. architecture:react-component-runtime INV1 forbids rendering a known-wrong state and repairing it in a follow-up Effect, and INV3 asks an Effect to name what it synchronizes. Opening from the click handler, or naming the constraint the frame waits for, would conform.
| expect(shown, `t=${t}`).toBeGreaterThanOrEqual(previous); | ||
| previous = shown; | ||
| } | ||
| expect(crossing).toBeGreaterThan(3); |
There was a problem hiding this comment.
This assertion fails at this head on every run (expected 0 to be greater than 3), also when the test runs alone and on Node 24.22.1, the version CI pins. Either the inserted row no longer waits for the crossing row, or the test no longer describes the code.
| await act(async () => undefined); | ||
| fireEvent.click(toggle() as HTMLElement); | ||
| const below = screen.getByTestId('below'); | ||
| expect(below.style.translate).not.toBe(''); |
There was a problem hiding this comment.
This fails at this head on every run (expected '' not to be ''): right after the toggle the content below the tree carries no translate. The other tests in this file pass.
| on: a transition that travels becomes a cross-fade carrying the same change | ||
| of state. An entrance lands in place and fades in, an exit fades out in | ||
| place, and any other move fades through. A navigation push or pop | ||
| cross-fades over 0.5 s on the ease-in-out curve; a sheet, a full-screen |
There was a problem hiding this comment.
A 0.5 s push or pop cross-fade, spring-timed sheet fades and a Reduce Motion spring for menus contradict the current design:reduced-motion DR2 ("MUST change opacity only and MUST finish in 200 ms or less") and DR4, which this record does not list among the records it amends. The stack's animator already follows the 200 ms rule, so FR8 should defer to it or propose the amendment to its owner.
| @@ -177,6 +190,13 @@ const STATIC_EXPORTS = { | |||
| types: './dist/utils/interactionOverlay.stylex.d.ts', | |||
| default: './dist/utils/interactionOverlay.stylex.js', | |||
| }, | |||
| // Proposed; pending owner review (spec:AST-094 OQ4): the Reduce Motion | |||
There was a problem hiding this comment.
AST-094 has no OQ4 (only OQ1 and OQ2), and this entry publishes @astryxdesign/core/motion/reducedMotion.stylex with no @experimental marker or changeset, so its first release would be stable API under spec:AST-017 FR21, while .changeset/touch-experimental-subpaths.md says nothing is added to any other subpath. Marking it experimental, or leaving it out of the export map until an owner settles it, avoids that.
| @@ -247,9 +255,15 @@ export interface DefineThemeInput { | |||
| * // Snappy: { fast: 100, medium: 250, ratio: 0.75 } | |||
| * // Default: { fast: 175, medium: 410, slow: 975, ratio: 0.75 } | |||
| * // Cinematic: { fast: 200, medium: 500, slow: 1200, ratio: 0.7 } | |||
| * | |||
| * // A softer sheet and a slower reduced-motion fade: | |||
There was a problem hiding this comment.
The example sets no reduced-motion fade, and a theme cannot set one: timing.reducedMotionCrossFadeMs is no longer a role, and design:reduced-motion DR4 forbids a theme value that lengthens it. Dropping "and a slower reduced-motion fade" keeps the docs true.
… hit area; the target never drops below 44 px) into the tree expansion change
… hit area; the target never drops below 44 px) into the list updates change
…in when the pointer or theme changes `--size-touch-target` was read once, when a row mounted, and only in px: a theme's `3.75rem` kept the 44 px reach, and a pointer that turned coarse, or a theme that changed, left the size the row first read. The size is now resolved in px, rem (through the root's font size) or em (through the element's), still never below 44 px, and read again when the primary pointer changes or the theme does. Red first (unit): a 3.75rem size and a 3em size at 20 px give 60 px; a pointer turning coarse re-reads it; a theme change re-reads it.
…again on a pointer or theme change) into the tree expansion change
…again on a pointer or theme change) into the list updates change
There was a problem hiding this comment.
This can't be approved yet: against main the diff still carries about 41,200 authored lines from fourteen open predecessor pull requests, for a change of its own of about 1,800 lines (the browser proofs and the harness's drag({cancel})). Please retarget this branch onto #8337, or land the predecessors in order, so that only the proofs remain; that also returns List hasAnimatedChanges and TreeList's handle-less expansion motion, which conflict with spec:AST-105 FR6 and FR2, to #8106 to settle.
- Please make the proofs assert one
onReordercall per drop and none per cancel: with the callback doubled, every proof in the new spec still passes, because the story'smoveKeyis idempotent, whilespec:AST-103's FR3 verification lists two callbacks for one drag as a failure. - The hit-area code and tests cite a List FR10 and an AST-094 FR43 that the current specs don't have; the rule they follow is
spec:AST-094FR42.
[Automated review]
| 0, | ||
| ); | ||
| } | ||
| expect(await labels(page)).toEqual(ROWS); |
There was a problem hiding this comment.
With onReorder called twice per drop, every proof in this file still passes, because the story applies moves with an idempotent moveKey. Counting the story's onReorder calls (for example in a data- attribute on the list) and asserting one after the drop proof and none here would cover spec:AST-103 FR3's "two callbacks for one drag, a callback on cancel".
| expect(styles.handle).toEqual(styles.plain); | ||
| }); | ||
|
|
||
| test('a mouse drags a handle at once and the rows jump, with no motion', async ({ |
There was a problem hiding this comment.
This title and the comment at line 1125 still describe the slide-aside look ("the rows jump", "Rows that make room jump"); in the stationary look no row makes room during a drag, and the assertion only checks that nothing animates.
…inst a fine pointer astracat's 13:21Z review: the finger-target case capped every hint button, Done included, below 44 px, which neither shows that no control grows for a finger (spec:AST-094 FR42) nor allows a button that is larger by itself. It now opens the same hint where the primary pointer is fine and requires each button to add the same space around what it shows (its glyph, or its text's width and line) under both pointers; the theme's type scale may set larger text for a coarse pointer, which the comparison leaves out. The invisible hit layer still only grows, up to 44 px or the button's own size. Failing-first: with a coarse-only 2 px of block padding on the buttons, the old cap passed and this case fails (Move up adds 4 px of height).
…e touch target size from the theme) under the reorder values
astracat's 13:19Z review: the browser proofs read only the order a drop leaves, and the story's moveKey is idempotent, so an onReorder called twice for one drop passed them, while spec:AST-103 FR3 asks for one call per drag and none for a cancelled one. The List stories count the moves their onReorder reports on the list (data-moves); the touch drop and the mouse drop each add exactly one, and a cancelled drag adds none. Failing-first: with the engine reporting each drop twice, the touch and mouse drop proofs failed (expected 1, received 2); the cancel proof passed, as it should.
There was a problem hiding this comment.
This can't be approved yet: against main the diff still carries about 41,500 authored lines from open predecessor pull requests, for a change of its own of about 1,800 lines (the browser proofs, the story's move count and the harness's drag({cancel})). Please land the predecessors in order so that only the proofs remain; that also returns List hasAnimatedChanges and TreeList's handle-less expansion motion, which conflict with spec:AST-105 FR6 and FR2, to #8106 to settle. The new move count closes the earlier gap: with onReorder called twice per drop, both drop proofs now fail.
- Main's
scripts/touch-support-bundle.test.mjsfails “is absent from the root entry” on this head, because the root entry'screateReorderablebrings the gesture modules with it, so the merge needs an engineering owner's decision on #8671 first. - Several comments still cite a List FR10 and an AST-094 FR43 that the current specs don't have; the rule they follow is
spec:AST-094FR42.
[Automated review]
| * Off by default; with a mouse nothing moves. | ||
| * @default false | ||
| */ | ||
| hasAnimatedChanges?: boolean; |
There was a problem hiding this comment.
hasAnimatedChanges is a boolean for the job spec:AST-105 FR6 gives useAnimatedLayout's handle (“no other API does this job; and it is not a visual boolean”). It arrives here from #8106, and #8687 proposes that List take the handle instead; until a record changes, it should leave this diff with its predecessor.
| export function useTreeExpansionMotion( | ||
| isExpanded: boolean, | ||
| ): TreeExpansionMotion { | ||
| const animates = useTouchPresentation().isTouch; |
There was a problem hiding this comment.
This turns the expansion motion on under any finger with no animatedLayout handle, and the rows after the group follow it. spec:AST-105 FR2 says a component without a handle keeps its behavior “and content after it takes its place at once”; #8691 proposes the TreeList record that would allow the group's own motion while the rows after it stay put without a handle.
| @@ -172,6 +172,9 @@ export * from './Outline'; | |||
| // Overflow list | |||
| export * from './OverflowList'; | |||
|
|
|||
| // Reorder (a collection's `reorderable` prop) | |||
| export * from './reorderable'; | |||
There was a problem hiding this comment.
This re-export brings createReorderable, its engine and the gesture modules into the root entry, so main's scripts/touch-support-bundle.test.mjs fails “is absent from the root entry” on this head. spec:AST-103 FR6 wants the root export, so the check needs an engineering owner's decision on #8671 before this merges.
| ); | ||
| // Under a finger, an interactive row's hit target grows toward the touch | ||
| // target where its neighbors leave room, with no visual or layout change | ||
| // (`spec:AST-094` FR42; `component:List` FR10, #8571): useTouchHitArea keeps the row's insets live, and |
| expect(styles.handle).toEqual(styles.plain); | ||
| }); | ||
|
|
||
| test('a mouse drags a handle at once and the rows jump, with no motion', async ({ |
There was a problem hiding this comment.
This title and the comment at line 1141 still describe the slide-aside look (“the rows jump”, “Rows that make room jump”); in the stationary look no row makes room during a drag, and the assertion only checks that nothing animates.
|
Posted by Cindy's agent on her behalf: closed and reopened only to restart this PR's CI on the current workflow after tonight's queue-wide cancel; nothing in the PR changed. |
… hold A divided List row keeps its dividers while it is pressed, as it does today, and Core keeps no selection hold: products show a row kept selected through ListItem's isSelected. This removes the touch press's divider hiding (its ListItem styles, docs, changeset, tests and browser proofs) and the selection hold (selectionHold.ts, its held press state in the shared press code, its internal deselect constants, the row focus-return lines, story, changeset and proofs). The shared test helpers, the press-coverage guard and the press release's own clock stay. Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
The previous merge left List.tsx's hasDividers JSDoc unresolved and the List stories empty. This restores the stories (without the selection hold demo) and the plain hasDividers line. Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
There was a problem hiding this comment.
This can't be approved yet: against main the diff still carries about 39,300 authored lines from open predecessor pull requests, for a change of its own of about 1,800 lines (the browser proofs, the story's move count and the harness's drag({cancel})), and it no longer merges cleanly into main. Please land the predecessors in order, each brought in line with main's current specs, then rebase so only the proofs remain; all 26 proofs pass in Chromium at this head.
createReorderablenow brings the gesture runtime into the package root, so the repository'sscripts/touch-support-bundle.test.mjsfails on this head.spec:AST-103FR3 lets a pen on a fine primary pointer drag without a hold, which the reorder does not support yet, and FR4 asks reorder mode to show its insertion line as soon as the mode starts.- List
hasAnimatedChanges, TreeList's row shifts without ananimatedLayouthandle, and the touch-only gesture host conflict withcomponent:ListFR12,spec:AST-105FR2 andspec:AST-094FR19 onmain.
[Automated review]
| return () => {}; | ||
| } | ||
| const onPointerDown = (event: PointerEvent) => { | ||
| if (event.pointerType !== 'mouse' || event.button !== 0) { |
There was a problem hiding this comment.
Only a mouse gets the hold-free drag here, and the touch hold that takes a pen is attached only under (pointer: coarse), so a pen on a fine primary pointer cannot drag at all. spec:AST-103 FR3: “with a mouse, or a pen where the primary pointer is fine, a drag from the handle starts without a hold”.
| */ | ||
| private placeModeLine(list: HTMLElement): void { | ||
| const row = this.rowOf(this.modeHandle); | ||
| if (row == null || this.lastStep == null) { |
There was a problem hiding this comment.
With lastStep null the mode line is cleared, so reorder mode starts with no insertion line until the first move. spec:AST-103 FR4: “while the item is in reorder mode, the same accent insertion line a drag shows marks the item's place”.
| @@ -172,6 +172,9 @@ export * from './Outline'; | |||
| // Overflow list | |||
| export * from './OverflowList'; | |||
|
|
|||
| // Reorder (a collection's `reorderable` prop) | |||
| export * from './reorderable'; | |||
There was a problem hiding this comment.
This re-export pulls listReorderEngine and through it the gesture modules into the root entry; scripts/touch-support-bundle.test.mjs fails “is absent from the root entry” on this head. Loading the engine with a dynamic import when a List first receives the object would keep the root clean.
| * Off by default; with a mouse nothing moves. | ||
| * @default false | ||
| */ | ||
| hasAnimatedChanges?: boolean; |
There was a problem hiding this comment.
component:List FR12 now gives row update motion to the animatedLayout handle (“List MUST take an animatedLayout handle”), and spec:AST-105 FR6 says “it is not a visual boolean”, so this prop should become the handle before it ships.
| * | ||
| * ONLY WHERE THE PRIMARY POINTER IS COARSE | ||
| * | ||
| * - Recognizers attach, and the host registers listeners, only while the |
There was a problem hiding this comment.
spec:AST-094 FR19 now reads “Recognizers attach for every pointer, with FR43's exceptions”, and architecture:react-component-runtime INV13 says the host attaches “with no touch-only query”; this host still installs nothing unless (pointer: coarse) matches.
| expect(await cursor()).toBe('grab'); | ||
| }); | ||
|
|
||
| test('where the primary pointer is fine, no recognizer attaches: the gesture host listens to nothing', async ({ |
There was a problem hiding this comment.
The assertion (no document touch listener during a mouse drag) still holds under FR24, but the title states the superseded rule; FR19 now attaches recognizers for every pointer.
Depends on #8337. Review only this PR's change: vjeux/astryx@21babff...a23519f
Why
Some of the List reorder's promises can only be checked in a real browser with real touch input: the hold before pickup, the preview tracking the finger, the drop's glide, and autoscroll. Without proofs, a later change could break them unnoticed.
What led to it
This is part of the UIKit-parity program, which makes Astryx's components behave like their iOS counterparts on touch screens. #8139 and #8337 make these promises. On a touch screen:
ListReorder.interaction.browser.spec.tsadds these proofs. The test harness'sdraggains acanceloption, and the List stories count reported moves.Options and tradeoffs
Why this choice
These promises are about real input and motion, so they need a browser. WebKit runs every proof its driver supports.
How to check it
To see what the proofs check, open Storybook
Core/List→ Reorderable on a phone:I ran the proofs in Chromium and WebKit, and all pass; WebKit skips what its driver can't produce. Each proof failed first where it guards a fix. With the engine reporting each drop twice, both drop proofs fail, and a 30 px shift injected into the preview fails the right-to-left proof.
hasAnimatedChangesand TreeList's expansion come from #8106 and wait for Cindy's ruling through #8687 and #8691. Two reds come from elsewhere:touch-support-bundle, which #8671 fixes, andpr-rtl, main's newer RTL test from 0472c4e.Evidence
On iOS 26.2: the preview stops at the top, a pinch zooms, and Reduce Motion: nothing travels.
Stack (merge in this order): the list stack through #8106 → #8138 → #8139 → #8337 → this.