Skip to content

test(List): the List reorder's browser proofs in Chromium and WebKit - #8140

Draft
vjeux wants to merge 1497 commits into
meta:mainfrom
vjeux:uikit/c13-list-reorder-proofs
Draft

vjeux wants to merge 1497 commits into
meta:mainfrom
vjeux:uikit/c13-list-reorder-proofs

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

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:

  • A 150 ms hold picks a row up, and a flick scrolls.
  • The preview follows the finger exactly while the other rows stay still.
  • The drop glides on at the finger's speed and reports one move; a cancel reports none and leaves nothing behind.
  • Near the edge, autoscroll moves in whole steps.
  • A pinch zooms, and a busy main thread still glides.
  • In right-to-left, the handle sits on the left.

ListReorder.interaction.browser.spec.ts adds these proofs. The test harness's drag gains a cancel option, and the List stories count reported moves.

Options and tradeoffs

  • jsdom tests only, which are fast but can't scroll, pinch or glide.
  • Real-browser proofs, which are slower and partly Chromium-only, where WebKit's driver can't produce the input.

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:

  1. Hold a grip and drag.
  2. Flick a grip.
  3. Drag a row to the bottom edge.

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. hasAnimatedChanges and 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, and pr-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.

@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 Oct 28, 2026 4:29am UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟡 Reviewed (for maintainers only) Oct 28, 2026, 4:58 AM UTC

@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Oct 24, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 24, 2026
@vjeux
vjeux force-pushed the uikit/c13-list-reorder-proofs branch from b90033e to 168ae59 Compare October 24, 2026 08:09

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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).
  • useListUpdateMotion reads every row's layout after each commit even when hasAnimatedChanges is off.
  • The lint job 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(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The 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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This 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.

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

Current run: The current CI run concluded cancelled. Current analysis is unavailable.

View Storybook for this PR · View Sandbox for this PR


Generated by PR Enrichment workflow | View current CI run

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 lint job runs out of JavaScript heap on this head, while it passes on main.
  • useListUpdateMotion reads every row's layout after each commit even when hasAnimatedChanges is 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]

Comment on lines +159 to +184
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},
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

These 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.

Comment on lines +78 to +103
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;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This 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).

Comment thread packages/core/src/List/selectionHold.ts Outdated
Comment on lines +84 to +93
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;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment on lines +562 to +565
this.announce(
this.messages?.dropped(slot + 1, rows.length) ?? '',
'polite',
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This 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.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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).
  • useListUpdateMotion reads every row's layout after each commit even when hasAnimatedChanges is off.
  • A move that onReorder declines 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]

@vjeux

vjeux commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

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 lint: the job runs out of JavaScript heap on the CI runner with the stacked diff. ESLint passes on the spec this commit adds.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.tsx and useTreeExpansionMotion.test.tsx.
  • Touch-only motion ships statically in every TreeList and List import (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.stylex would 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'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This 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('');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread scripts/sync-exports.js Outdated
@@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread packages/core/src/theme/defineTheme.ts Outdated
@@ -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:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The 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.

vjeux added 5 commits October 27, 2026 05:29
… 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

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 onReorder call per drop and none per cancel: with the callback doubled, every proof in the new spec still passes, because the story's moveKey is idempotent, while spec: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-094 FR42.

[Automated review]

0,
);
}
expect(await labels(page)).toEqual(ROWS);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

With 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 ({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This 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.

vjeux added 8 commits October 27, 2026 06:37
…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.
…c381 and meta#8336 c3c119a through meta#8139's base) under the List reorder's browser proofs
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
…r-target proof; meta#8106 781c381 below it) under the List reorder's FR43 follow-up

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.mjs fails “is absent from the root entry” on this head, because the root entry's createReorderable brings 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-094 FR42.

[Automated review]

* Off by default; with a mouse nothing moves.
* @default false
*/
hasAnimatedChanges?: boolean;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This 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';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

component:List has no FR10 on main (it ends at FR9; FR10 is only proposed in #8571). The rule this follows is spec:AST-094 FR42; cite that alone until #8571 lands.

expect(styles.handle).toEqual(styles.plain);
});

test('a mouse drags a handle at once and the rows jump, with no motion', async ({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This 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.

@cixzhang

Copy link
Copy Markdown
Contributor

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.

cixzhang and others added 5 commits October 27, 2026 20:51
… 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>
…tion hold) as a dependency

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>
…tion hold) as a dependency

Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
…tion hold) as a dependency

Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 28, 2026
…tion hold) as a dependency

Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

  • createReorderable now brings the gesture runtime into the package root, so the repository's scripts/touch-support-bundle.test.mjs fails on this head.
  • spec:AST-103 FR3 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 an animatedLayout handle, and the touch-only gesture host conflict with component:List FR12, spec:AST-105 FR2 and spec:AST-094 FR19 on main.

[Automated review]

return () => {};
}
const onPointerDown = (event: PointerEvent) => {
if (event.pointerType !== 'mouse' || event.button !== 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

With 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';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This 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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 ({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The 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.

@vjeux
vjeux marked this pull request as draft October 29, 2026 22:31

This branch was successfully deployed

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants