Skip to content

spec(AST-094): touch presentations feel native by default - #8409

Merged
cixzhang merged 8 commits into
mainfrom
spec/ast-094-touch-native-default-r4
Oct 25, 2026
Merged

cixzhang merged 8 commits into
mainfrom
spec/ast-094-touch-native-default-r4

Conversation

@cixzhang

@cixzhang cixzhang commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Supersedes #8394, #8378, #8316 and #8052. It carries vjeux's record, credited as co-author, plus every owner ruling since. The record itself contains no pull request references.

Latest change: FR36 no longer lists the row swipe. A row swipe keeps spec:AST-057's own release rule, and spec:AST-057 is not amended. Current-state impact drops row swipes from the release-decision line. Current main is merged in.

Exact decision needed

Approve spec:AST-094: touch presentations get one native-feeling behavior by default on every touch device, iOS and Android alike. Open questions: OQ5 (the menu's drift allowance), OQ6–OQ8 (opening a sub-menu without a lift, the opening haptic, a withdrawn tentative open), OQ9 (where a menu sits on a compact touch screen) and OQ10 (a sub-menu with its parent visible). Platform look and feel is a non-goal tracked separately. Release velocity is governed by spec:AST-100.

Existing authority searched

  • Current claims this record names as amended for touch presentations:
    • component:Selector DEC-1 (the adaptive query);
    • spec:AST-013 FR3 (an Android Chrome support row);
    • spec:AST-025 FR14 (nested scrolling);
    • design:user-states DR1/DR3;
    • the press treatment of family:buttons;
    • module:DropdownMenu/useMenuPress FR6.
  • Current claims it keeps and cites:
    • module:DropdownMenu/useMenuPress FR4;
    • design:user-states DR4;
    • spec:AST-043 FR1;
    • spec:AST-012 FR9 (compact);
    • design:reduced-motion;
    • architecture:public-component-api INV14, INV16;
    • spec:AST-017 FR3, DEC-23;
    • spec:AST-002 FR3;
    • architecture:react-component-runtime INV5/INV7;
    • family:overlay-dismissal AV6.
  • Why none of them settles it: no current record states native touch behavior as the default.

Decision

  • One behavior on every touch device: measured values with sources, the same on every platform unless a measured, sourced difference exists. Touch motion is themeable, with the measured values as theme defaults; gesture thresholds stay internal.
  • Motion and gestures: compositor motion a finger can catch. Recognizer arbitration and readings follow the native model. Two press profiles. Exits as native.
  • Reduce Motion: governed by design:reduced-motion DR2 and DR4.
  • When it applies: one rule for when the touch presentation applies, a coarse primary pointer plus the spec:AST-012 FR9 compact definition where a presentation depends on size. The touch presentation applies after hydration.
  • Defaults and cost: a moved default is restored by the caller's presentation choice (or longPressDelayMs for useMenuPress). Touch work costs nothing where the primary pointer is fine, and a finger there gets the browser's defaults (AST-057's row swipe keeps its own per-event read).
  • Menus and popovers under a finger (FR26–FR35):
    • The lift decides, and the highlight follows the finger with one lit row.
    • A lift outside keeps a menu open.
    • A menu opens on press: at once in a fixed bar, or after 150 ms with no scroll inside a scroll view. The opening finger's handoff applies.
    • Edge autoscroll while the finger is held.
    • Menus and Popover grow from the trigger and shrink back.
    • A pan outside dismisses a menu (Popover's dismissal is unchanged), and pinch stays available.
    • ContextMenu's long press and lift.
    • A sub-menu drills in as DropdownMenu's does.
    • A presentation takes no input while it grows.
    • useMenuPress hands its finger rules here. DropdownMenu, ContextMenu and Popover point here.
  • Release decisions (FR36–FR40):
    • One decision at the release, from projected travel against a commit point.
    • Moving back cancels, and the settle continues the finger.
    • A keyboard and mouse path stays available.
    • Velocity comes from spec:AST-100.
    • A row swipe keeps spec:AST-057's own release rule.

Rejected alternatives

  • Per-platform behavior tables (one native-feeling behavior on every touch device is the ruling).
  • A generic restoring prop per moved default (presentation is the way back, INV16 and DEC-23).

Claim scope and non-goals

  • Out of scope: pointer presentations, scroll physics, which components adopt a touch presentation, and platform-matched look and feel (tracked separately).
  • Not governed here: public surface and experimental marking (spec:AST-017), and how Lab reaches private modules.

Evidence and implementation relationship

The values come from UIKit read on the simulator, Apple documentation, WWDC sessions and WebKit's source. Implementation stacks on this record. Implementations: #8078 (menu press and handoff), #8080 (menu grow and collapse), #8187 and #8198 (Popover grow and pan-outside dismissal), #8201 (ContextMenu lift), #7308 (ContextMenu under a finger), #8341 (the release decision), #8220 (sub-menus in place), #8223 (the presentation hold), #8195 (the menu cell profile); #8089 (menu placement on a phone) is OQ9.

Writing rubric results

  • R1 narration: none. Amendments are present-tense rules, and the record carries its deciders.
  • R2 research in the record: none. Measurements are cited, not reproduced.
  • R3 evidence naming a change: none.
  • R4 rejected alternatives over one line or outside a decision: none.
  • R5 pull request or issue references remaining: none in the records.
  • R6 ruled open questions still listing options: none. OQ1, OQ2 and OQ5–OQ10 are unanswered.

Scope

  • This PR contains specification records only.
  • It records observable behavior and preserves equivalent internal implementations (FR1, FR9, FR13 and FR24 are stated as observable contracts, architecture:knowledge-contracts INV18).
  • Any named internal mechanism is intentionally public, names its dependent caller/system, and explains why an equivalent implementation would not satisfy the contract.
  • Verification states the evidence layer and failure signal without prescribing CI job/workflow topology.
  • It records one intentional durable decision, not a rescue for a separable tagalong.
  • The canonical knowledge owner, affected current claims, and overlapping open work are named; any ownership collision is resolved or called out for human decision.
  • Adjacent API, visual, accessibility, composition, and implementation facts remain outside scope unless this exact decision depends on them.
  • Public text and artifacts contain no internal Meta context.

Validation

  • pnpm check:knowledge: aligned.
  • Prettier: clean.
  • git diff --check: clean.

Touch presentations get one native-feeling behavior by default on every
touch device: measured values, compositor motion a finger can catch,
recognizer-tracked gestures, two press profiles, Reduce Motion per
design:reduced-motion, menus and popovers under a finger (FR26-FR35), and
one release decision for every drag that commits (FR36-FR40). Touch motion
is themeable through use-named roles, except motion in step with the
on-screen keyboard. The current records it amends point to it.

Retries #8394, whose review rounds were spent, carrying its last revision
plus the owner's 2026-10-25 rulings: DEC-5 (FR4 cites the generated motion
role table), DEC-6 (touch media-query CSS counts as after hydration), DEC-7
(watchers are passive, never cancelled, and follow the scroll), and
interaction-modality INV10 (Lab reads release velocity only through Core's
recognizers). FR17 and FR19 follow.

Co-authored-by: vjeux <197597+vjeux@users.noreply.github.com>
@vercel

vercel Bot commented Oct 25, 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 25, 2026 6:16pm UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🔄 In progress (for maintainers only) Oct 25, 2026, 6:07 PM UTC

@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Oct 25, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 25, 2026
@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

Current run: The current change has no visual or preview-producing scope. Visual checks are not applicable; preview links are not shown.

The exact-head preview is not available yet.


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 spec is not ready to approve yet: no engineering owner has approved spec:AST-094 or its amendments to nine current records at this head. Please fix the two points below in a new commit; then an engineering owner (the author included, as one) records the decision by commenting /approve-spec <full head SHA> on this pull request.

  • The rewritten architecture:interaction-modality INV10 has Lab import Core's recognizers from an internal module, but Lab reaches Core only through Core's package exports, which architecture:public-component-api INV12 makes public, while FR4 says the gesture machinery publishes no public API; please name the supported import path in INV10 (and narrow FR4 to match), or keep the Lab-local tracker sentence.
  • spec:AST-094's Current-state impact says interaction-modality's "invariants are unchanged", and interaction-modality's Deciding specs section still says no other specification changes it; please name the INV10 change there and add spec:AST-094 as a deciding spec.

[Automated review]

Comment on lines +160 to +162
threshold compares the tracker's release velocity. Lab reads release
velocity only through Core's recognizers (`velocity()` on a pan, or on a
scroll view's pan for a scrolling surface), from Core's internal gestures

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Lab reaches Core only through Core's package exports (a peer dependency, no alias to Core's source). Reading the recognizers "from Core's internal gestures module" therefore needs a Core export, which architecture:public-component-api INV12 makes public, while AST-094 FR4 says this machinery publishes no public API. Please name the supported import path here (and narrow FR4 to match), or keep the sentence under which Lab keeps one local tracker with the same blend.

Comment on lines +436 to +437
- `architecture:interaction-modality`: the touch-presentation conditions
(FR13) sit beside the shared modality state; its invariants are unchanged.

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 says interaction-modality's invariants are unchanged, but the bullet at line 464 and this pull request rewrite INV10. interaction-modality's Deciding specs section also still says no other specification changes it. Please name the INV10 change here and add spec:AST-094 to that section.

- interaction-modality is unchanged here: the INV10 amendment (Lab keeps no
  copy of the release-velocity filter) moves to a separate change, and
  Current-state impact says the record's invariants are unchanged.
- FR17 is a plain pointer to spec:AST-100.
- FR4 keeps "publishes no public API" without access or experimental
  wording.

Co-authored-by: vjeux <197597+vjeux@users.noreply.github.com>
Whether the motion and gesture machinery is public is spec:AST-017's to
govern, not a touch behavior; FR4 keeps only the theme motion roles.

Co-authored-by: vjeux <197597+vjeux@users.noreply.github.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 spec is not ready to approve yet: no engineering owner has approved spec:AST-094 and its amendments to current records at this head. An engineering owner (the author included, as one) records that decision by commenting /approve-spec <full head SHA> on this pull request. Two small record fixes are needed whatever the decision:

  • spec:AST-057 now says the row's slide and spring motion plays on theme motion roles, but neither it nor FR4 names those roles or their sourced defaults; please name them, or keep the durations as source constants until they are measured.
  • FR19 and FR24 forbid recognizers and touch listeners "on a fine pointer" without saying whether that is the primary pointer, while spec:AST-057 reads the pointer per event; please say which condition governs a finger on a touchscreen whose primary pointer is fine.

[Automated review]

Comment thread docs/specs/AST-057-row-actions/spec.md Outdated
- **Gesture tuning.** Axis-lock distance, commit ratio, fling velocity,
resistance past the panel: behavior constants in source, not public API and
not design tokens (the same ruling the touch press model received for its
clocks). The row's slide and spring motion plays on theme motion roles

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 hands the row's slide and spring motion to theme motion roles, but FR4's table has no row-swipe role and this record measures none, so Item's swipe has no named role or sourced default to read once it may no longer play its constants. Please name which FR4 roles the slide and the spring-back play (or add the row's measured roles with sources), or keep the durations as source constants until they are measured.

pan that shares its axis with native scrolling. `pinch-zoom` MUST stay
unless a pinch or rotation recognizer is attached.
- **FR19 — Claims are made in time and only by an owner.** Recognizers attach
only under a coarse pointer; on a fine pointer no recognizer attaches and no

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"On a fine pointer" is not defined here: FR13 tests the primary pointer and lets a presentation ask for its own record's conditions, while spec:AST-057 reads the pointer per event (a touchscreen laptop gets the swipe). Read as the primary pointer, this withholds the row-swipe recognizer AST-057 grants there. Please say which condition FR19 and FR24 mean, and align AST-057's platform line if the answer changes it.

…ants

- FR19 and FR24 mean the primary pointer: where it is fine, no recognizer
  attaches and a finger gets the browser's defaults for this record's
  behaviors (FR13, DEC-6; AST-017 FR59, DEC-25). spec:AST-057 keeps its
  per-event pointer read for the row swipe.
- spec:AST-057 is unchanged: no row-swipe role is measured, so its slide
  and spring durations stay behavior constants; FR4 and Current-state
  impact no longer claim them.

Co-authored-by: vjeux <197597+vjeux@users.noreply.github.com>
Brings in current main (AST-025's tooltip exception merges cleanly beside
this record's FR14 line) and drops the row swipe from FR36 and the
Current-state impact: a row swipe keeps spec:AST-057's own release rule,
and AST-057 is not amended.

Co-authored-by: vjeux <197597+vjeux@users.noreply.github.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 spec is not ready to approve yet: no engineering owner has approved spec:AST-094 and its amendments to current records at this head. An engineering owner (the author included, as one) records that decision by commenting /approve-spec <full head SHA> on this pull request. Two record fixes are needed whatever the decision:

  • component:Popover FR9 makes a pan outside close a Popover under a finger, but its Compatibility and migration section still says the released default is preserved and FR25 does not list it; please state the changed touch default and the caller's control (hasLightDismiss={false}) there, and add it to FR25 or say why it is touch-only behavior under architecture:public-component-api INV16.
  • FR19 gives a finger on a touchscreen whose primary pointer is fine the browser's defaults for this record's behaviors, while FR26–FR28 apply whenever a finger drives a menu and useMenuPress hands its finger rules to them; please say which rule a finger in a menu follows on such a device, for example by keeping the per-gesture pointer read as the row swipe does.

[Automated review]

| FR6 | Component anatomy contains the caller trigger and content, one painted Popover surface, and the optional fallback close control. Popover owns no Header, Body, or separate shared-hook surface part. | Current source, docs target inventory, and tests | Accepted anatomy; stale consumer anatomy corrected by this contract |
| FR7 | The painted surface has one broad canonical target, `popover`; composed components may add one authoritative component-specific refinement target. | Owner direction on 2026-08-31 plus current target inventory | Accepted canonical contract; covered by focused target tests |
| FR8 | The surface padding is one spacing-scale rung applied on the painted surface. Popover defaults to step 3 (the released inset); `0` paints a flush surface; a direct `usePopover` composition paints none unless it asks for a rung. The rung sits where a theme's `padding` on the `popover` target replaces it. | PR #6683 and its Popover tests; `architecture:container-padding/INV8` (padding alone does not enroll) | Pending owner review (DEC-4); implemented and covered by the Popover surface-padding suite |
| FR9 | Under a finger, Popover grows out of its trigger and shrinks back into it, a pan outside dismisses it while its outside dismissal is on, and a touch during its growth is ignored, as `spec:AST-094` FR31, FR32 and FR35 state. A mouse keeps today's entrance and dismissal. | `spec:AST-094` FR31, FR32, FR35 | Pending implementation; tests land with it |

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 moves Popover's released touch dismissal: a pan outside now closes it while hasLightDismiss is on, where today the page scrolls and it stays open. Line 60 still says "Released default preserved: yes", and AST-094 FR25 lists only four moved defaults. Please state the touch default change and its caller control (hasLightDismiss={false}, which also stops tap-outside dismissal) under Compatibility and migration, and add Popover to FR25 or say there why it is touch-only behavior under architecture:public-component-api INV16.

Comment on lines +235 to +241
- **FR19 — Claims are made in time and only by an owner.** Recognizers attach only where the primary pointer
is coarse (FR13, DEC-6). Where the primary pointer is fine, no recognizer
attaches and no touch listener is registered (FR24), so a finger on a
touchscreen whose primary pointer is fine gets the browser's defaults for
this record's behaviors (`spec:AST-017` FR59, DEC-25). This rule governs
this record's behaviors only; `spec:AST-057` keeps its per-event pointer
read for the row swipe, as its record states. A recognizer that begins MUST claim its touches by cancelling

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 gives a finger on a fine-primary touchscreen the browser's defaults for every behavior in this record, but FR26–FR28 apply whenever a finger or pen drives a menu, and useMenuPress FR1–FR2 now hand their finger halves to them while the module still reads the pointer type per event. Please say which rule a finger in a menu follows on such a device: keep the per-gesture read, as the row swipe keeps AST-057's, or name the change as a moved default. FR24's touch-only timer limit affects the held-finger open the same way.

cixzhang and others added 2 commits October 25, 2026 10:23
…y pointer

- Popover: under a finger a pan outside now dismisses it while its outside
  dismissal is on (FR32). Popover's compatibility section and FR25's
  moved-defaults list name it, with hasLightDismiss={false} as the
  caller's control.
- FR26-FR28 and FR30 apply when the primary pointer is coarse (FR19); on a
  fine-primary touchscreen a finger in a menu keeps today's behavior
  (AST-017 FR59, DEC-25), with no per-gesture pointer read.

Co-authored-by: vjeux <197597+vjeux@users.noreply.github.com>
…hanged

Popover keeps today's dismissal under a finger: FR32 covers anchored menus
only, FR25's moved-defaults list and Current-state impact drop Popover, and
Popover's record keeps its released default (its FR9 points only to FR31's
grow and FR35's presentation hold).

Co-authored-by: vjeux <197597+vjeux@users.noreply.github.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 spec is not ready to approve yet: no engineering owner has approved spec:AST-094 and its amendments to current records at this head. An engineering owner (the author included, as one) records that decision by commenting /approve-spec <full head SHA> on this pull request. Two record fixes are needed whatever the decision:

  • On a touchscreen whose primary pointer is fine, the menu rules say a finger keeps today's behavior with no per-gesture pointer read, while useMenuPress FR1–FR3, FR6, FR7 and FR9 still hand every finger to FR26–FR30 and FR24 rules out the timer behind today's 500 ms held-finger open; please state one rule for that finger (for example the module's per-event finger model) and either exempt it from FR24 or list the lost held open in FR25.
  • FR32 now closes every anchored menu on a pan outside, including one with presentation="popover", but FR25 lists no such moved default and the Current-state impact and DropdownMenu's compatibility section say presentation="popover" keeps the released behavior; please carve that presentation out of FR32 or record the change and narrow that wording.

[Automated review]

and Selector's listbox, whose options count as rows) when a finger or pen
drives it and the primary pointer is coarse (FR19); where the primary pointer
is fine, a finger in a menu keeps today's behavior (`spec:AST-017` FR59,
DEC-25), and menus make no per-gesture pointer read; FR29 applies to DropdownMenu's trigger; FR34 applies to menus;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

On a touchscreen whose primary pointer is fine, “keeps today's behavior” and “menus make no per-gesture pointer read” pull in opposite directions: today's press model reads the pointer type per gesture (it keeps a menu open after a finger's lift outside but closes it after a mouse's), and useMenuPress FR1–FR3, FR6, FR7 and FR9 still hand every finger to FR26–FR30. FR24 also rules out the timer behind today's 500 ms held-finger open there. Could this say which rule that finger follows, for example the module's per-event finger model as the row swipe keeps AST-057's, and either exempt it from FR24 or list the lost held open in FR25?

closed. This is its exit under FR7 (`family:overlay-dismissal` AV6); under
reduced motion FR8 applies. A menu or popover presented as a bottom sheet
follows the sheet instead.
- **FR32 — A pan outside dismisses.** Under a finger, an open anchored menu MUST close as a touch outside it

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 closes every anchored menu on a pan outside, including one with presentation="popover", where today native light dismiss leaves it open while the page scrolls. FR25 lists no such moved default, and the Current-state impact and DropdownMenu's compatibility section say presentation="popover" keeps the released behavior. Could FR32 carve that presentation out, or could FR25 and both menu records record the change and narrow that wording to the open and the lift?

- useMenuPress FR1-FR3, FR6, FR7 and FR9 hand a finger or pen to AST-094
  FR26-FR30 only where the primary pointer is coarse; where it is fine, a
  finger keeps today's behavior, including the 500 ms held-finger open.
  FR24 exempts that released behavior explicitly, so FR25 needs no new
  entry.
- FR29 applies under the same condition as FR26-FR28.
- FR32: a menu whose caller passes presentation="popover" keeps its
  released outside dismissal (FR25).

Co-authored-by: vjeux <197597+vjeux@users.noreply.github.com>
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 20eceaa

@github-actions github-actions Bot added spec-auto-merge Auto-merge was enabled by the spec owner gate and removed needs:spec-owner-review Current knowledge records await owner approval labels Oct 25, 2026
@cixzhang
cixzhang merged commit 23de092 into main Oct 25, 2026
50 checks passed
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
The only conflict is docs/specs/AST-094-touch-presentations/spec.md
(add/add): main's copy is the decided record (meta#8409, current), so it
replaces this branch's draft.
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
The only conflict is docs/specs/AST-094-touch-presentations/spec.md
(add/add): main's copy is the decided record (meta#8409, current), so it
replaces this branch's draft.
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
The only conflict is docs/specs/AST-094-touch-presentations/spec.md
(add/add): main's copy is the decided record (meta#8409, current), so it
replaces this branch's draft.
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
The only conflict is docs/specs/AST-094-touch-presentations/spec.md
(add/add): main's copy is the decided record (meta#8409, current), so it
replaces this branch's draft.
@github-actions
github-actions Bot deleted the spec/ast-094-touch-native-default-r4 branch October 26, 2026 06:31

This branch was successfully deployed

1 active deployment
Preview — 20eceaa2 Deployed Oct 25, 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. spec-auto-merge Auto-merge was enabled by the spec owner gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant