Skip to content

[preview, do not merge] FR43 trackpad rescope: pager, one step per swipe - #9172

Draft
cixzhang wants to merge 2005 commits into
mainfrom
preview/8676-step-pager
Draft

cixzhang wants to merge 2005 commits into
mainfrom
preview/8676-step-pager

Conversation

@cixzhang

Copy link
Copy Markdown
Contributor

Preview build only, for a live trackpad check of the AST-094 FR43 rescope proposed on #8676. Do not merge, review or land; it closes after the check.

Rescope: the pager and swipe rows take one discrete step per two-finger trackpad swipe. A sheet keeps a trackpad pull that releases when the fingers lift (momentum onset). An idle end releases still, so a slow drag springs back, and only a far pull or a real fling closes. Form and required sheets rubber-band and bounce as under a finger. The mouse wheel drives no pan.

cixzhang and others added 30 commits October 26, 2026 21:56
…ser test) into the arena fixes

The host-owner changeset keeps this PR's own listeners for the scroll lock
and finger-scroll watch, with #8336's lone-finger scroll-edge hold.
…sture system (spec:AST-094 FR24, FR43)

utils/pressFeedback.ts reaches gestures/coarsePointer.ts, so a page whose
only touch work is press feedback carries a gesture module, and a bundle
check that counts gestures/ code finds it in the root and hooks entries.
The coarse-pointer query belongs with the environment queries. This test
walks the controller's import graph and fails until it reaches no module
under gestures/.
…ries in utils/ (spec:AST-094 FR43)

gestures/coarsePointer.ts becomes utils/coarsePointer.ts, beside
utils/touchQueries.ts, with its test; the gesture host's selection lock,
the press controller and the pointer test helper import it from there. The
press controller's import graph now reaches no module under gestures/, so
a page whose only touch work is press feedback carries none of the gesture
system. No behavior changes.
CI's lint rejected the three Array<T> types the previous commit added
(@typescript-eslint/array-type); the test now names its record once
(WatchTransition) and reuses it.
…e arena's reset

The arena resets a recognizer to possible after it ends, as UIKit does,
and the transition listener records that too, so the last transition is
possible: the test now asserts the ended-then-possible tail.
…er, #8589's watcher test, its lint fix; #8076 3bfd7c3; the coarse-pointer query in utils/) into #8293
…n utils/) into #8356

# Conflicts:
#	packages/core/src/gestures/GestureHost.ts
#	packages/core/src/gestures/fingerScroll.ts
#	packages/core/src/gestures/pageHold.ts
#	packages/core/src/gestures/selectionLock.ts
#	packages/core/src/utils/pressFeedback.ts
… FR19, FR24)

Under a fine primary pointer: each element that uses a gesture keeps one
passive pointerdown listener and the page none while idle; a mouse or a
pen pans, its page listeners lasting only the press; a press consumer
takes a mouse press with no feedback-only mode (DEC-21); a pan writes its
touch-action for a touchscreen laptop's finger (FR18); perEventPointer is
taken as any attach, with no warning; a secondary button starts nothing.
All but the last fail until the host attaches for every pointer.
… the scroll brake for a finger only (spec:AST-094 FR11, DEC-21)

A mouse or a pen press paints at once, inside a scroller too; a mouse
press on a scroller a flick set moving still presses, as the brake is a
finger's. The brake case fails until the tracker brakes only a touch.
… (spec:AST-094 FR43)

A drag from the edge band begins a screen-edge pan for a finger only,
unless the caller allows another pointer. The mouse and pen case fails
until the recognizer defaults to touches.
… FR19, FR24, DEC-21)

Recognizers and press consumers attach for a mouse, a pen and a finger
alike. Where the primary pointer is fine, each element with an enabled
recognizer or consumer keeps one passive pointerdown listener and the page
has none: a press on it (a mouse's primary button, a pen, or a finger)
installs the host's page listeners until that press's contacts end. The
action group's own fine-pointer path folds into this one, so a group is
served like every attachment.

- touch-action is written for every pointer, a kept press's manipulation
  too (FR18); the element's blocking touchmove stays a coarse pointer's,
  so under a fine one the element keeps its one listener (FR24).
- A press consumer takes a mouse press as a press (DEC-21): no
  feedback-only mode; PressTracker brakes only a finger's press (FR11).
- The screen-edge pan allows touches only unless its caller allows another
  pointer (FR43).
- perEventPointer is accepted and does nothing; the fine-pointer warning
  goes.

Tests that encoded the coarse-only design now state FR43's; a finger's
pointerdown comes before its touchstart in them, as in a browser.
…th real mouse drags (spec:AST-094 FR43, FR24)

Under a desktop's mouse the host still writes touch-action, leaves the
page no listener while idle and warns for nothing; a real mouse drag pans
both the bare page's pan and the fixture's horizontal pan, and the page's
touch listeners are gone again after the release.
…(spec:AST-094 FR18, DEC-13)

A screen-edge pan attached to a stack container, as wide as the screen,
with a touch 4 px from its edge: inside the 40.2 px band the pan decides at
UIKit's 15 px whatever the element's width, and then tracks the travel past
it. From several edges too. Both fail while a wider element decides at
FR19's 10 px.
…'s width (spec:AST-094 FR18, DEC-13)

FR18: inside the 40.2 px band a screen-edge pan decides at UIKit's 15 px
screen-edge hysteresis; outside it the author's scrolling is untouched and
FR19's 10 px governs every other claim. The pan decided at 10 px on any
element wider than the band, so NavigationStack's edge pan, attached to the
stack container, began 5 px early. The edge zone still decides only the
touch-action; a touch outside every band still fails at once. The two
cases that pinned the 10 px decision go.
…hatever the element's width) into the experimental touch exports
…e keeps that finger until it lifts) into the experimental touch exports
…than the band (spec:AST-094 FR18, DEC-22)

FR18 as amended in #8631 (DEC-22): the band's pan-x claim and 15 px start
hold only where the screen-edge pan is attached to an element within the
band; on an element that reaches beyond it, whose touch-action cannot
claim per touch, a touch in the band decides at FR19's 10 px like every
other claim. This restores the 10 px decision 3d85c30 removed and keeps
15 px on an element within the band. The cases pin DEC-22 both ways (a wide
element with a touch at x=4 begins at 10, an in-band strip at 15, and from
several edges at 10 on a wide element); the 10 px cases fail on 3d85c30.
…dge band decides at 10 px again, DEC-22) into the experimental touch exports
…ides at 15 px on an element within the band and at 10 px beyond it, DEC-22; #8641 bec2283) into #8270
A slow swipe carries its travel across short gaps and commits at 24 px;
the step flick starts the settle at about 2500 px/s.

@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 preview is on hold for an engineering owner's decision. It intentionally changes spec:AST-094 FR43, which today says a pan takes wheel and trackpad scrolling ("a pan by drag and by wheel"), and it carries #8692's new FR41 text, which is not yet approved. If an owner accepts the rescope, FR43 and its verification row need updating on main (and AST-057 DEC-7, if swipe rows step too), and an engineering owner records approval of the FR41 text by commenting /approve-spec <full head SHA> here; the author may do so as an engineering owner. Whatever the decision, these need fixing before any of this lands:

  • The step flick stamps arena input up to 24 ms after the wheel that committed it (GestureHost.ts:2121-2123), so a press that follows within that window is re-timed (FR16).
  • After a followed trackpad pan releases at momentum onset, the remaining momentum wheels fall through to the page (GestureHost.ts:1968-1987).
  • .changeset/gestures-wheel.md, .changeset/uikit-pager-every-pointer.md, the gestures README and wheel.test.ts:358 / usePager.test.ts:1064, :1085 still describe the wheel behavior this head removes.
  • At 37,964 authored lines, the rescope should land as its own small change on top of #8692's stack.

[Automated review]

Comment on lines +2121 to +2123
this.arena.moved([{id, ...at(12), time: time + 8}]);
this.arena.moved([{id, ...at(52), time: time + 24}]);
this.arena.ended([{id, ...at(52), time: time + 24}]);

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 samples are stamped time + 8 and time + 24, after the wheel that committed the step. The arena only moves its clock forward (GestureArena.clampTime), so a press within 24 ms is re-timed and timers due in that window run early: the FR16 problem endWheel guards against below. Stamp no sample later than the committing wheel, or let the pager take a discrete step without synthetic contacts.

if (
gesture != null &&
gesture.ids.length > 0 &&
gesture.step == 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.

A lifted follow (momentum onset, line 2066) has already ended its contact, so wheelHasTaker is false on the next momentum wheel. This branch then hands the gesture to the page (ids: []), and the remaining momentum is not prevented. Exempting gesture.follow?.lifted here, as steps are, keeps it absorbed.

'@astryxdesign/core': patch
---

[chore] Internal: a pan takes wheel and trackpad scrolling by default, a mouse wheel's notches included (`allowedScrollTypes`, UIKit's `allowedScrollTypesMask`; `[]` opts out), along its own axis and after the scrollers under the pointer; the screen-edge pan takes none; and a pinch takes a ctrl-wheel, as a trackpad pinches (AST-094 FR43). A watcher pan never takes the wheel, and a wheel another handler already prevented stays the page's. Not exported from the package entry point.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

At this head a pan takes no scroll input by default (PanGestureRecognizer.ts:111 returns []), so this changeset now describes the opposite of what ships.

@cixzhang cixzhang closed this Oct 31, 2026
@cixzhang
cixzhang deleted the preview/8676-step-pager branch October 31, 2026 19:09
@cixzhang
cixzhang restored the preview/8676-step-pager branch October 31, 2026 19:16
@cixzhang cixzhang reopened this Oct 31, 2026

@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 preview is on hold for an engineering owner's decision. It intentionally changes spec:AST-094 FR43, which today says a pan takes wheel and trackpad scrolling ("a pan by drag and by wheel"), and it carries #8692's new FR41 text, which is not yet approved. If an owner accepts the rescope, FR43 and its verification row need updating on main (and AST-057 DEC-7, if swipe rows follow), and an engineering owner records approval of the FR41 text by commenting /approve-spec <full head SHA> here; the author may do so as an engineering owner. Whatever the decision, these need fixing before any of this lands:

  • After a followed trackpad pan releases at momentum onset, the remaining momentum is no longer prevented and scrolls the page (GestureHost.ts:2006-2026).
  • That release drops the held deltas and ends at least four wheel intervals after the last move, so it reads velocity 0 and a short fling springs back (GestureHost.ts:2094-2107).
  • usePager.ts:964-969 zeroes the velocity of short pen and mouse drags too, not only wheel input, so the same flick pages under a finger but not under a pen.
  • The non-follow wheel timer now falls back to 180 ms (GestureHost.ts:2071), and .changeset/uikit-pager-every-pointer.md, the gestures README and seven tests still describe the behaviour this head removes.
  • At 38,236 authored lines, the rescope should land as its own small change on top of #8692's stack.

[Automated review]

Comment on lines +2094 to +2101
if (this.wheelSegment?.liftNow === true) {
// Momentum began: the fingers lifted before the held deltas.
// Drop them and release at the fingers' travel and speed; the
// rest of the momentum never moves the pan.
f.lifted = true;
f.heldX = 0;
f.heldY = 0;
this.arena.ended([{id: gesture.ids[0], x: gesture.x, y: gesture.y, time}]);

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 held deltas are dropped and the contact ends here, at least four wheel intervals after the last arena.moved (three held events plus this one, wheelSegmenter.ts:38, :88). Past 40 ms of stillness scrollVelocity() reads 0 (VelocityTracker.ts:195-201, AST-100 FR2). That happens whenever wheels are more than 10 ms apart, and often even at 120 Hz. pageForRelease then picks the nearest page, so a short fling springs back. Moving the pan by the held travel at the lift before ending, as the previous head did, keeps the fingers' speed.

Comment on lines +964 to +969
if (
pan.pointerType !== 'touch' &&
Math.abs(dragPosition - offsetOf(dragStartIndex)) < 0.1 * pageWidth
) {
velocity = 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.

pointerType !== 'touch' also matches mouse and pen drags, not only wheel input (wheel contacts are 'mouse'). A short pen or mouse flick that pages under a finger now returns to its start page, against AST-100 FR4 ("the same motion gives the same velocity for touch, pen, and mouse pointers"). Scoping this to recognizer.trackedTouches[0]?.scrollType != null, as the clamp at :934 does, keeps it to the trackpad.

},
gesture.step != null && !gesture.step.committed
? WHEEL_STEP_CARRY_MS
: gesture.follow?.endMs ?? WHEEL_FOLLOW_END_V_MS,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Every gesture without a follow (a ctrl-wheel pinch, a mouse-notch pan) now ends on the 180 ms sheet timer. The gap check at :2001, the release stamp at :2443 and the README still use WHEEL_END_MS (100 ms), and wheel.test.ts:450 and :580 pin 100 ms. Did you mean gesture.follow?.endMs ?? WHEEL_END_MS?

@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 preview is still on hold for an engineering owner's decision, and the latest push changes no code. It intentionally changes spec:AST-094 FR43, which today says a pan takes wheel and trackpad scrolling ("a pan by drag and by wheel"), so if an owner accepts the rescope, FR43 and its verification row need updating on main first (and AST-057 DEC-7, if swipe rows follow). It also carries #8692's new FR41 text, which an engineering owner approves by commenting /approve-spec <full head SHA> here; the author may do so as an engineering owner. Whatever the decision, these need fixing before any of this lands:

  • After a followed trackpad pan releases at momentum onset, the rest of the momentum is no longer prevented and scrolls the page (GestureHost.ts:2006-2026).
  • That release drops the held deltas and comes at least four wheel intervals after the last move, so it reads velocity 0 and a short fling springs back (GestureHost.ts:2094-2107).
  • usePager.ts:964-969 zeroes the velocity of short pen and mouse drags too, not only wheel input, so the same flick pages under a finger but not under a pen.
  • The non-follow wheel timer falls back to 180 ms (GestureHost.ts:2071), and .changeset/uikit-pager-every-pointer.md, the gestures README and seven tests still describe the behaviour this head removes.
  • At 38,236 authored lines, the rescope should land as its own small change on top of #8692's stack.

[Automated review]

…he fingers' speed (stamped at their last move); unit case

@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 preview is still on hold for an engineering owner's decision; the latest push fixes the short-fling spring-back and restores the 100 ms end for pinches and mouse-notch pans. It intentionally changes spec:AST-094 FR43, which today says a pan takes wheel and trackpad scrolling ("a pan by drag and by wheel"), so if an owner accepts the rescope, FR43 and its verification row need updating on main first (and AST-057 DEC-7, if swipe rows follow). It also carries #8692's new FR41 text, which an engineering owner approves by commenting /approve-spec <full head SHA> here; the author may do so as an engineering owner. Whatever the decision, these need fixing before any of this lands:

  • After a followed trackpad pan releases at momentum onset, the rest of the momentum is not prevented and scrolls the page (GestureHost.ts:2006-2027).
  • usePager.ts:964-969 zeroes the velocity of short pen and mouse drags too, not only wheel input, so the same flick pages under a finger but not under a pen.
  • Seven wheel and pager tests fail on this head, and .changeset/uikit-pager-every-pointer.md, the gestures README, the story text and the description describe behaviour the pager no longer has.
  • At 38,271 authored lines, the rescope should land as its own small change on top of #8692's stack.

[Automated review]

This branch was successfully deployed

1 active deployment
Preview — f9d8c138 Deployed Oct 31, 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