Skip to content

feat(Toast): every pointer swipes a toast, on the shared gesture host (spec:AST-094 FR43) - #8678

Draft
vjeux wants to merge 1788 commits into
meta:mainfrom
vjeux:uikit/c25-toast-every-pointer
Draft

vjeux wants to merge 1788 commits into
meta:mainfrom
vjeux:uikit/c25-toast-every-pointer

Conversation

@vjeux

@vjeux vjeux commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #8144 and #8676 (and their predecessors). Review only this PR's change: vjeux/astryx@d54551c...9118ec7 and the commits vjeux@363d754, vjeux@786d25e, vjeux@c027f5f, vjeux@81bd53b and vjeux@55fe123 (the rest merge #8676 and #8144)

Primary intent

Toast's swipe takes every pointer and wheel scrolling, and its own swipe (a fine pointer, or presentation="toast") runs on the shared gesture host (spec:AST-094 FR43). Other templates don't fit: it changes behavior.

User or maintainer impact

  • A mouse now swipes a toast in both presentations; once the swipe takes the drag, its text selection is cleared and none follows (FR41).
  • A mouse wheel or trackpad scroll toward the edge swipes it too once nothing under the pointer, the page included, can scroll that way (FR43).
  • Under Reduce Motion, a mouse's, trackpad's or pen's banner release cross-fades only on a coarse primary pointer, and otherwise changes at once (FR43, design:reduced-motion DR3).
  • The own swipe begins after 10 pt, not 8 px (FR17).
  • A second finger no longer cancels a started swipe; one landing earlier leaves the page its pinch (FR10).
  • Unchanged: the follow, fade, release rule and exit.

Problem and solution fit

Toast kept private touch and pen listeners with their own slop, second-finger rule and velocity tracking. It now attaches a one-way PanGestureRecognizer and reads its travel and release velocity. Both pans let the host manage the card's touch-action (FR24).

Authority

Change and evidence

  • useToastGesture.ts: the pan replaces the listeners. Both pans decline a scroll the page can take; the banner marks only a finger's release as touch. Docs, and a presentation="notification" story.
  • Toast.spec.md: FR4 states the mouse, wheel, trackpad and second finger.
  • jsdom, red first: no own touch listener; a second finger keeps a started swipe; a mouse drag, a notch and a trackpad scroll swipe both presentations; a scrollable page keeps the scroll.
  • Real input, Chromium and WebKit, red first: a mouse drag across the text dismisses and selects nothing; a notch and a trackpad scroll dismiss; a scrollable page scrolls until its edge; under Reduce Motion a mouse release of the banner changes at once. With presentation="toast", a finger's drag toward the edge dismisses; away leaves it.

Scope

  • This PR has one primary intent.
  • Independently removable deltas were removed or split.
  • Public text and artifacts contain no internal Meta context.

Size: +1,313 / −563 (tests 949). Toast's import (the CI's measurement): JS 95,551 → 94,818 B gzip; no CSS change.

Testing

Writing rubric results

R1 narration: FR4's wheel, trackpad, and second-finger sentences, the compatibility line, and the two edited current-state bullets state what the system does, in the present tense; none found.
R2 research: none in the record.
R3 evidence: the FR4–FR5 verification row names its inputs and test files, not changes; no basis cell changed.
R4 rejected alternatives: none.
R5 change references: none.
R6 open questions: none; the record keeps "None".

vjeux and others added 30 commits October 26, 2026 21:10
… is down off the sheet (spec:AST-094 FR10)

The lone-finger hold counts only the fingers the pan tracks, so a second
finger on plain page content left the sheet's finger held, and the moves
of a pinch across the sheet's edge were cancelled. This test fails until
the host counts every finger on the screen.
…is down anywhere (spec:AST-094 FR10)

The lone-finger hold counted only the fingers the pan tracks, so a second
finger on plain page content left the sheet's finger held, and a pinch
across the sheet's edge had its moves cancelled. The host now applies a
recognizer's hold only while the move's event reports one finger on the
screen; the pan's own count still covers two fingers on the sheet.
An attach reads the element's own will-change in a task of its own. A
shared animator (animatorFor) is never detached, so its node can leave the
document with that read still waiting, and the read then forces a style
pass on a node with no style: a test run that mounts and unmounts many
pagers piled up such reads until a later wait timed out. The read now
skips a node out of the document; a promotion that needs it reads it then,
as one on a node attached in the same task does. A detach and dispose
already cancel it.

The new test fails on the previous code (one read of a removed node); the
in-task read test now puts its element in the document, where the read
happens.
…ter the browser takes the touch

A host that stopped feeding the finger at the takeover and still delivered
the lift would have passed. Each transition now records the list's
scrollTop, and the test expects a 'changed' while the list is scrolled
(spec:AST-094 FR20: it follows the scroll to the lift).
…her browser test) into the arena fixes

The host-owner changeset keeps this PR's own listeners for the scroll lock
and finger-scroll watch, with meta#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.
…finger, meta#8589's watcher test, its lint fix; meta#8076 3bfd7c3; the coarse-pointer query in utils/) into meta#8293
…nter query in utils/) into meta#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.
…e coarsePointer move) into uikit/c25-toast-swipe
…nues the release

spec:AST-094 FR37: a release whose velocity points back toward rest springs the card back, however far out it is. FR38: after a dismissing release the retract starts at the card's release velocity; a release faster than the curve can start carries the card out sooner. architecture:component-style-authoring INV6: the touch module imports the settle row alone. design:reduced-motion DR2: under reduced motion on a touch screen, a toast-presentation card fades out before its row closes. architecture:react-component-runtime INV3: the mount-flag Effect is gone; the two-frame check reads whether the card is still in the document. The follow test reads long tasks only in Chromium: the harness's long-task log refuses to answer in WebKit, which has no Long Tasks API.
# Conflicts:
#	packages/core/src/Toast/__tests__/ToastSwipe.interaction.browser.spec.ts
#	packages/core/src/Toast/toastSwipe.test.ts
#	packages/core/src/Toast/toastSwipe.ts
… 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.
vjeux added 26 commits October 29, 2026 10:24
…s once

Where nothing that claims is attached, the host's touchend listener is
passive and cannot cancel the engine's own click after a retargeted tap
clicks what is drawn. The shared click guard swallows that late click
whatever is attached; this case pins it, as asked on meta#8293.
meta#8360 now carries the review fixes of the foundation it holds. The two
conflicts are this pull request's own files:
- GestureHost.ts: meta#8360 counts the attachments that ask for an action
  group; here the fine-pointer group listener and the per-event recognizer
  set are gone (every pointer is served, FR43), so the merge keeps the
  count and drops those two.
- GestureHost.test.ts: both sides added the same assertion to the
  second-finger case; the merge keeps this side's comment. meta#8360's
  action-group lifetime case counts a group's own pointerdown under a fine
  pointer, which this pull request replaces with one passive pointerdown
  on any element that uses a gesture (its own idle-listener case), so the
  merge leaves it out.
… attached, in this host's terms

meta#8360's action-group lifetime case pins that an element stays a group
while any attachment asks and stops with the last. Here every element
that uses a gesture is served as a group under a fine pointer, so the
same invariant reads as the element's one passive pointerdown: with two
gestures attached, detaching either keeps it, and detaching the last
removes it. It passes on this head.
meta#8665 now carries meta#8360's new head (75392b2), which carries the
gesture foundation with the harness meta#8045 and meta#8046 landed on main, so
this pull request no longer carries their old copies. The merge is
clean.
…ady prevented or uncancelable is the page's, and a notch release never outruns the press that ends it

From the review of this pull request:
- a watcher pan (claimsTouches: false) got a non-passive wheel listener
  and prevented the page's wheels along its axis (spec:AST-094 FR20,
  DEC-7);
- a first wheel an app's handler already prevented, or one that cannot be
  cancelled, was taken by the pan;
- a notch gesture's release was dated 100 ms past its last wheel even
  when a press ended it sooner, so the arena's monotonic clock moved that
  press's times later and its long press began late (FR16).
All three fail.
…dy prevented or uncancelable stays the page's, and a notch release never outruns the press that ends it

- Only a recognizer that claims gets the element's non-passive wheel
  listener or takes a wheel gesture: a watcher pan leaves the page its
  wheels (spec:AST-094 FR20, DEC-7).
- A wheel gesture whose first wheel another handler already prevented, or
  that cannot be cancelled, is the page's from its first step.
- A mouse wheel's notch gesture releases at the earlier of 100 ms past its
  last wheel and the input that ends it (a press, the next gesture's first
  wheel), so the arena's clock never moves that input's times later
  (FR16).
…what stays the page's, and when a notch release is dated

The gestures README and the wheel changeset now say that a watcher pan
never takes the wheel, that a gesture whose first wheel another handler
already prevented (or that cannot be cancelled) stays the page's, and that
a mouse wheel's notches release at 100 ms past the last wheel or at the
input that ends the gesture sooner, never later.
This pull request carried meta#8666 only up to b246458; meta#8666's head adds
its main merge and its later harness text. The two conflicts are meta#8666's
harness files, resolved with meta#8666's author keeping both sides:
- internal/interaction-harness/README.md: meta#8360's module and
  engine-limits tables with meta#8666's webInput row and its wheel and pen
  rows, both engine columns (the same text as meta#8666 merged onto meta#8360);
- harness.interaction.browser.spec.ts: meta#8360's header with meta#8666's wheel
  and pen clause, and every test from both sides; this pull request's own
  changes to the backward-run timing stay.
…esture host's subscriptions

The gesture host subscribes no click: the one cancelling click subscriber
is the click guard's, added at a window's first arm and kept. The release
note, the stream's header and the gestures README listed click with the
host's touchend and contextmenu; they now name the guard's own.
meta#8360's input-stream notes no longer list click among the gesture host's
subscriptions. The merge is clean.
… lift and the browser's menu, keeps a kept press's finger, and goes when the page hides

From the review of this pull request, three cases fail on its head:
- an armed press installed the page listeners without the claiming
  ones, so a claimed finger's touchend stayed passive and the browser's
  menu opened during a claimed long press (spec:AST-094 FR21);
- a kept press got its blocking touchmove during an armed press, but its
  moves were cancelled only where the primary pointer is coarse, so a
  held stepper on a touchscreen laptop lost its finger (FR11, DEC-21);
- a page hidden mid-press cancelled the press but kept the page's
  listeners until the next lift anywhere (FR24).
…lift and the browser's menu, keeps a kept press's finger, and goes when the page hides

- A finger's or a pen's armed press now brings the claiming listeners
  (the cancelable touchend and the contextmenu canceller) with the page
  listeners, as wherever the host installs, so a claimed lift clicks
  nothing and a claimed hold opens no browser menu (spec:AST-094 FR21). A
  mouse's press needs neither and adds neither.
- A kept press keeps its finger under any pointer: its target blocks and
  its moves are cancelled once its press is delivered, not only where the
  primary pointer is coarse (FR11, DEC-21).
- A page hidden mid-press releases the armed press as a blur does, so the
  page's listeners go with the cancelled press (FR24).
…'s selection

An action group's press is a long press with no minimum duration, so it
claims at the press's pointerdown, before any movement. The drag
selection hold then cleared the page's selection, so a plain click on a
group's button left its Copy or Cut with nothing to act on. The case
fails on this head: the selection is gone after the pointerdown.
…age's selection

The drag selection hold cleared the page's selection when any mouse or
pen contact was claimed, but an action group's press claims at its
pointerdown, before it has moved or selected anything, so a plain click
on a group's button left its Copy or Cut with nothing to act on. A claim
before any movement now keeps the page's selection: the hold still
refuses a new one, and puts the page's back over a selection that
appears while it runs. A claim after movement clears what its drag
selected, as before (spec:AST-094 FR41).

The case before the new one now waits for its hold to end a task after
the lift, so no hold outlives that test.
…t every pointer does

- GestureHost's header, the gestures README and coarsePointer.ts no
  longer describe the coarse-only host: a finger or a pen takes the
  touch-down lock under any primary pointer, a kept press's finger is
  kept during a fine pointer's press too, and recognizers attach for
  every pointer while the page listeners last a press (spec:AST-094 FR19,
  FR24, FR41, FR43).
- The fine-pointer selection case asserted the replaced rule and passed
  only because it read iOS WebKit's inline lock without iOS WebKit: it
  now asserts the lock a fine pointer's finger and pen take, and that a
  mouse takes none.
- FR41's sentence and its verification row say that a press claimed at
  its own pointerdown, before it moves, keeps the page's selection, while
  a drag that moved before its claim loses what it selected.
…ion, in the present tense

The sentence said a drag that moved before its claim loses the selection
it started, but the hold clears the page's selection then, whatever made
it; and an unclaimed mouse drag kept "today's" behavior, a phrase that
ages. It now says a claim after movement clears the page's selection, a
press claimed at its own pointerdown keeps it, and an unclaimed mouse
drag keeps its released behavior.
meta#8665's review fixes: an armed press under a fine pointer brings the
claiming listeners, keeps a kept press's finger and goes when the page
hides; a press claimed at its own pointerdown keeps the page's selection;
FR41 says what a claimed drag does with the selection; meta#8360's
input-stream notes. The merge is clean.
Toast.spec.md records what this branch ships: the `presentation` concept
(`adaptive` by default, `toast`, `notification`), the notification swipe
in FR4 and its retract in FR5, the released default it changes under a
finger, and the current-state bullets and gap text the notification
presentation closes (a release moving back springs back, the settle starts
at the release velocity, and reduced motion fades on a touch screen). The
toast presentation keeps its listed gaps: net travel, a theme-timed settle,
a second touch that cancels, and no projection.
Toast.spec.md records what this branch adds: a mouse drag, a wheel and a
trackpad scroll swipe a toast in both presentations (FR4, with the page
keeping a scroll it can still take and a claimed drag holding off text
selection), a second touch never ends a swipe that began, and only a
finger's release is a touch release under reduced motion. The current-state
bullet on the mouse goes; the toast presentation keeps its listed gaps (net
travel, a theme-timed settle, no projection, and a reduced-motion fade that
follows the primary pointer).

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

Approval of this pull request waits on an engineering owner's decision. This head writes new rules that main does not state into spec:AST-094 FR41 (a mouse or pen drag a gesture claims clears the page's text selection) and into Toast's record (the presentation concept and the FR4 and FR5 rewrites). An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Separately, please rebase as #8076, #8121, #8144, #8360, #8665, #8666 and #8676 land, or retarget onto the immediate predecessor's branch, so that the diff shows only this pull request's nine Toast files instead of 37,002 changed lines.

[Automated review]

acts as a finger's; a mouse makes the context request by a right click, not
a long press (FR43), and otherwise keeps its released behavior
(`spec:AST-017` FR59; DEC-9).
(`spec:AST-017` FR59; DEC-9). A mouse or pen drag that a recognizer claims

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 adds normative text that FR41 on main does not state: there a mouse "otherwise keeps its released behavior", and the verification row fails any change to mouse selection. It needs an engineering owner's approval for this head (/approve-spec <full head SHA>), or it can stay in #8665, where it originates.


- Released default preserved: `yes`
- Compatibility class: records shipped behavior; no public change
- Released default preserved: `no`: under a finger the default `adaptive`

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 changes Toast's current record. The presentation concept, the FR4 and FR5 rewrites, and this released-default change need an engineering owner's approval for this head before they govern.

This branch was successfully deployed

1 active deployment
Preview — 55fe123e Deployed Oct 29, 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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants