Skip to content

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

Closed
cixzhang wants to merge 4 commits into
mainfrom
spec/ast-094-touch-native-default-r2
Closed

cixzhang wants to merge 4 commits into
mainfrom
spec/ast-094-touch-native-default-r2

Conversation

@cixzhang

Copy link
Copy Markdown
Contributor

Supersedes #8316 (whose three review rounds were spent), which superseded #8052. It carries vjeux's record, credited as co-author, plus every owner ruling since. The record itself contains no pull request references.

What changed from #8316's last head:

  • The record is current, with approved_by and its Decider lines naming the owner. Writing them is not approval: the owner's /approve-spec at this exact head is.
  • FR34 follows DropdownMenu's drill-in model (FR11/FR20 unchanged). A sub-menu with its parent visible is OQ10, a look-and-feel question.
  • FR4's roles cover row swipes, Popover and sub-menus, with purpose-led names (architecture:theme-tokens INV3). The bounce-back is not listed (OQ1).
  • FR2 admits an owner decision that departs from a measurement.
  • The quick tap opens at its lift, and longPressDelayMs keeps a stated meaning.
  • New DEC-3 (motion themeable) and DEC-4 (open on press).
  • spec:AST-100 (release velocity) is cited as its pending record.

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). Module-level experimental subpaths are closed: motion and gestures stay internal (FR4). 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);
    • spec:AST-057 (Item's swipe constants and FR2);
    • 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 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. Touch work costs nothing under a fine pointer.
  • 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, 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.

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 (OQ3).
  • Open: module-level experimental subpaths (OQ4).

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, a Reduce Motion cross-fade
(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 purpose-named roles; thresholds stay internal.
useMenuPress, DropdownMenu, ContextMenu and Popover point to the new
requirements.

Retries #8316, whose review rounds were spent, carrying its last revision
plus the review fixes: the record is current with its deciders named, FR34
follows DropdownMenu's drill-in (a parent-visible sub-menu is OQ10), the
role list covers row swipes, Popover and sub-menus with purpose-led names,
FR2 admits an owner decision as a source, and the quick-tap and
longPressDelayMs wording is settled.

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 9:14am 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
❓ Needs maintainer input (for maintainers only) Oct 25, 2026, 9:23 AM UTC

@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval 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 merge yet: no engineering owner has approved spec:AST-094 at this head, and a few record fixes are needed whatever that decision is. An engineering owner records the decision on the new touch default, its amendments to seven current records and the move of useMenuPress's finger rules by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner.

  • Amend the six named current records that this head leaves unchanged (Selector DEC-1, AST-057, AST-025 FR14, user-states DR1 and DR3, AST-013 FR3, and the buttons family).
  • Land after spec:AST-100 is current, or state the release-velocity rule here, and state FR34's drill-in in FR34 itself.
  • Say how a useMenuPress caller keeps the released longPressDelayMs behavior when the trigger sits in a fixed bar.
  • Remove the remaining finger-on-a-tap text in useMenuPress, mark Popover FR9 as pending implementation, and name FR4's preset springs and curves by use.

[Automated review]

report and never the moment reported.
- **FR17 — Native readings.** A recognizer's thresholds MUST follow UIKit's
measured values (FR1, FR2). A pan begins at 10 pt of travel on either axis,
and its translation excludes that travel. Release velocity is governed by

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Release velocity is delegated to spec:AST-100, which is not on main yet. FR36–FR38 read that velocity, so landing this first leaves them without a governing input; land after AST-100 is current, or state the rule here.

moves a released default, such as Selector's `adaptive` presentation policy,
the way back and its release note are governed by
`architecture:public-component-api` INV16 and `spec:AST-017` DEC-23 and FR3.
This replaces `component:Selector` DEC-1's width-and-pointer query with 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.

This replaces Selector's DEC-1 query, but packages/core/src/Selector/Selector.spec.md still says "adaptive keeps its width-and-pointer query". The knowledge contracts require the owning record to change in the same pull request ("When current records disagree", step 5); the same applies to AST-057, AST-025 FR14, user-states DR1 and DR3, AST-013 FR3 and the buttons family.

| highlight | DOM focus, or a caller callback | one lit row; focus with `preventScroll` in a menu, `aria-activedescendant` in a picker | focus | `module:DropdownMenu/useMenuPress` | stable |
| stray-click window | 400 ms, or the next `pointerdown` | how long after a tracked release the browser's click is still swallowed | `MENU_PRESS_STRAY_CLICK_MS` | `module:DropdownMenu/useMenuPress` | stable |
| settle time | 300 ms | proposed: how long an opening press must last before its release may act | `MENU_PRESS_SETTLE_MS` | `module:DropdownMenu/useMenuPress` | proposed |
| long-press delay | as `spec:AST-094` FR29 states (none in a fixed bar; 150 ms in a scroll view) | proposed: how long a held finger waits on a trigger inside a scroll view before the menu opens; a trigger in a fixed bar opens on press (`spec:AST-094` FR29). A caller's `longPressDelayMs` sets that wait. | `longPressDelayMs` | `module:DropdownMenu/useMenuPress` | proposed |

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 released longPressDelayMs (@default 500) now sets only the scroll-view wait, so in a fixed bar it stops applying. Neither record says what restores the released behavior there for a hook caller, which spec:AST-017 FR3 asks a functional default change to name.

| 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 | Current with `spec:AST-094`; verified by its tests |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"verified by its tests": main has no Popover touch growth, pan-outside dismissal or growth hold yet, so this row should read pending implementation until those tests land.

- Selector DEC-1, AST-057, AST-025 FR14, design:user-states DR1 and DR3,
  AST-013 FR3 (an Android Chrome row for touch presentations) and the
  buttons family now point to or conform with AST-094.
- FR34 states the drill-in itself. AST-094 no longer amends AST-057 FR2's
  custom property.
- A useMenuPress caller that sets longPressDelayMs gets that wait in a fixed
  bar too, which keeps the released behavior.
- useMenuPress drops its finger-on-a-tap text; Popover FR9 is pending
  implementation.
- FR4's preset springs and curves are named by use.

Co-authored-by: vjeux <197597+vjeux@users.noreply.github.com>
open menu.
- **FR11 — Two press profiles.** Press feedback MUST follow one of two native
profiles. A control (UIControl) highlights on touch-down outside scroll
views, keeps tracking within its expanded bounds (70 pt), turns its highlight

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-posted from #8316, which this record supersedes; FR11 still reads the same here.

FR11's control profile can't be met on the web as written, and meeting it changes how a control is activated, so this needs a ruling on the spec before any code.

Measured in Chromium with touch emulation, a held finger on a button, on a page with nothing to scroll:

  • Default touch-action: by 20 px of drag the browser sends pointercancel. It claims the pan even with nothing to scroll (FR20), so the press is cancelled long before 70 pt, and a lift back on the button fires no click.
  • touch-action: pinch-zoom on the button: the pointer survives the drag, but once the finger has moved the browser fires no click, even for a lift inside the button.

So tracking out to 70 pt would only show a press the browser won't activate, unless two things change. Proposal, scoped to where UIKit tracks (a control with no scroller to take the touch: one in a fixed or sticky bar, as FR29 uses the term), under a coarse pointer only:

  1. A control in a bar gets touch-action: pinch-zoom, from static CSS decided before the touch. That matches UIKit, where a drag that starts on a toolbar button never scrolls the content, and it keeps page zoom (FR18).
  2. After a drag, a lift inside the 70 pt band activates the control from the press controller, and the browser's click for that gesture, if one fires, acts on nothing (the rule FR21 and FR26 already set for gestures and menu rows). React Aria's usePress activates this way.

Controls in scrolling content keep today's model: cancelled when the scroll takes the touch, as UIKit's are. A fine pointer is unchanged, and activation with detail === 0 always passes (FR21).

Question: should FR11 require this for controls in a bar? Or should it say that on the web a control's press ends when the browser takes the touch, and activation stays the browser's click?

@vjeux

vjeux commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

#8336 makes the gesture host the document's one touch owner. Against this record at ffb5a0b, FR10, FR19, FR20 and FR24 read as they did on #8316, so three of the four amendments proposed there are still open.

The fourth, one document touch owner (finger scrolls, the finger count, touch gates), is covered by FR24, FR10 and FR35, so it's dropped. #8336 already follows FR10: a second finger fails only a one-finger pan that hasn't claimed.

Accept, change or reject each of these, and #8336 will follow.

  1. FR19, a page hold. Add: "While a scroll lock under a modal presentation holds the page, it MAY cancel the moves of a one-finger touch that no recognizer owns, when that pan would scroll the page. A second finger is never cancelled (FR10)."
    Without it, useScrollLock keeps its own document listener, which does exactly this.
  2. FR19, a control that keeps its press through a drag. FR11 already lets "a control whose own record keeps its press through a drag" keep it, and that needs its touch held from the browser's pan. Add: "Once its press is delivered, such a control owns its touch: its element MAY register the blocking listener and cancel that finger's moves. The known limit above applies."
    Case: on Chrome for Android, a held stepper that slides onto its other button loses the touch to the page's pan at the first move, and its repeat stops.
  3. FR20, a watcher. Add: "A recognizer that never claims (a watcher) registers only passive listeners and is simultaneous with every recognizer. It is not cancelled when the browser takes the touch: it follows the scroll to the lift."
    Case: a tab bar that minimizes while the person scrolls, without delaying the scroll.

@vjeux

vjeux commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Update: FR4 now cites the generated motion role table and names the platform presets by use as paired in 1 (90f626b), so what follows is registrations, and a component table's roles appear in the generated table once it registers them. #8176 (e59ba91) makes the keyboard's spring a value, keyboard.spring; section 5 applies that rule to the rest.

FR4's role table compared with the role registry on #8076 (a71f4e5, 45 roles) and with the component tables in the open stacked pull requests (74 more roles, read at each table's newest head). Proposals below; the component tables mark their roles as proposed additions to FR4's table until you rule.

1. r2 roles we will register (20 not registered today). Thirteen are the platform presets the registry already measures, under the use names r2 gives them. This is how we read the names; please correct any pairing:

r2 role Value it takes (source in the table)
springs.fingerTracking SwiftUI interactiveSpring() (response 0.15, damping 0.86)
springs.emphasis SwiftUI .bouncy (0.5 s, bounce 0.3)
springs.settle SwiftUI .smooth (0.5 s, bounce 0)
springs.quickChange SwiftUI .snappy (0.5 s, bounce 0.15)
springs.viewChange UIKit UISpringTimingParameters()
springs.timedChange UIKit UIView.animate(springDuration:bounce:) defaults (0.5 s, bounce 0)
springs.stateChange SwiftUI Animation.default (response 0.55, damping 1)
springs.directChange SwiftUI .spring(response:dampingFraction:) defaults (0.5, 0.825)
timing.stateChangeMs Core Animation's implicit transaction, 250 ms
timing.curves.viewChange Core Animation's default curve (0.25, 0.1, 0.25, 1)
timing.curves.exit / entrance / move ease-in / ease-out / ease-in-out

The other seven (springs.popoverPresent, springs.popoverDismiss, springs.subMenuOpen, springs.subMenuBack, springs.rowSwipeOpen, springs.rowSwipeReturn, springs.rowSwipeCommit) are measured values in the popover, menu and row-swipe component tables; they move to these names.

2. Registered roles that r2 does not list.

  • The thirteen preset names above (springs.interactive, bouncy, smooth, snappy, uikitDefault, uikitSpringDuration, swiftUIDefault, swiftUISpring, timing.curves.caDefault, easeIn, easeOut, easeInOut, timing.implicitAnimationMs) give way to the use names in 1.
  • springs.menuReducedMotion goes: under reduced motion nothing springs (design:reduced-motion DR1).
  • scroll.bounce, the spring a coast plays back to the edge, is motion rather than a threshold. We propose adding it to the table.
  • scroll.paging.snap, which FR4 lists, gives way to the component role paging.settle (3, chore(core): UIKit's paging values and math, measured on iOS 26.2 #8145).

3. Component roles proposed as additions, each named <component>.<use> and measured with its source in its table. Where a value equals an r2 role's value, the component can use that role instead if it is the same use; those are marked "=".

74 roles in 18 components

Equal values: list.deselect.navigationPopMs = navigation.transitionMs; pageControl.switchCurve, search.activationCurve, zoomMedia.chrome.fadeCurve, zoomMedia.settle.curve = timing.curves.move; pageControl.fadeOutCurve = timing.curves.entrance; list.update.tableSpring, reorder.slide.spring, switchControl.travel = springs.navigationPush's value; tabBar.lensSettle, toast.settle = springs.settle; slider.dropSpring, switchControl.settle = springs.emphasis. Three entries are timing gates rather than motion (contextMenu.press.feedbackLatencyMs, contextMenu.press.returnLatencyMs, switchControl.minimumLiftMs); we would keep those internal under FR4's threshold rule.

An alternative to a hand-kept list: FR4 could cite the generated role table (packages/cli/assets/docs/motion.doc.mjs, which generate-motion-roles builds from every registered table and roles.test.ts checks), so a new component table cannot drift from the record.

4. One reading to confirm (FR13). FR13's "only after hydration" governs presentation decided in script (rendered markup). CSS keyed on the literal touch media query (a spinner, a progress bar, a skeleton, a refresh control's drawing) conforms: the server and hydrated markup are identical, and the browser applies the style at first paint.

5. System-driven motion is a value, not a role. Motion the system plays whatever the theme says, and any motion that must stay in step with it, keeps a measured value in its domain instead of a role: a retuned role could only move the page out of step with the system. That is why #8176 makes the keyboard's spring keyboard.spring, and the keyboard domain registers no roles. One registered role is the same kind: timing.keyboardNotificationMs, the length of the keyboard's own frame animation (the frame follows keyboard.spring, cut at that time). We propose it moves to the keyboard domain beside keyboard.spring. None of the component rows in 3 is that kind. Search's activation is UISearchController's own animator: its 383.3 ms equals the keyboard's notification duration but follows no keyboard, so search.activationMs stays a proposed role, and the earlier note pairing it with timing.keyboardNotificationMs is withdrawn. A popover that shrinks above a rising keyboard rides the keyboard's motion as geometry, with no role.

@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 merge yet: no engineering owner has approved spec:AST-094 at this head, and the record owner's open proposals on FR4, FR13 and FR20 still need rulings. An engineering owner records the decision by commenting /approve-spec <full head SHA> on the head that carries those rulings, and the author may do so as an engineering owner. Whatever is decided, FR4 needs one fix: it makes every touch spring and curve a theme-overridable role, which conflicts with the current keyboard ride in architecture:react-component-runtime INV11 and spec:AST-099 FR8, so either except motion that must stay in step with the on-screen keyboard or amend those records in this pull request.

[Automated review]

Comment on lines +97 to +103
- **FR4 — Touch motion is themeable.** Every spring, duration and curve a
touch presentation plays MUST be a theme motion role named for its use
(`architecture:theme-tokens` INV3); the generated motion role table
(`astryx docs motion`) lists every role with its kind and default. The role's default value is
its measured value (FR1, FR2), and a theme MAY override it through `motion`, at its root or
in an adaptation rule. A component MUST read touch motion through these roles
and MUST NOT play a literal. A theme value MUST reach the motion at its

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FR4 lets a theme override every spring, duration and curve a touch presentation plays, but a Core surface that rides the on-screen keyboard rides the keyboard hook's one published transition (architecture:react-component-runtime INV11) and moves within one frame of the keyboard's slide (spec:AST-099 FR8). A retuned role would move a sheet or composer out of step with the keyboard. Except motion the system plays, and motion that must stay in step with it, from the themeable roles, as the record owner proposed on this pull request, or amend those records here.

@cixzhang cixzhang closed this Oct 25, 2026
@cixzhang
cixzhang deleted the spec/ast-094-touch-native-default-r2 branch October 25, 2026 17:37
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
… a cancel clears a finger's row only where the primary pointer is coarse

From the review:
- The exported menuPressStep and its MenuPressGesture, MenuPressEvent,
  MenuPressEffect and MenuPressStep types keep their released shapes. The
  machine's handoff positions, handoffFrom and the settle clear live on
  internal types (MenuPressState, MenuPressInput, MenuPressOutcome,
  MenuPressTransition, menuPressTransition) that useMenuPress drives; the
  public step strips them, so a caller keeps the settle rule, as released.
  A test pins the public shapes.
- A cancel clears a finger's lit row only under the 'uikit' touch model (a
  coarse primary pointer); where it is fine, the highlight stays as before
  (spec:AST-094 FR19, AST-017 FR59). The hook test covers both.
- The changeset says useMenuPress with no longPressDelayMs now opens on
  press on a touch screen, and longPressDelayMs: 500 keeps the old hold.
- The spec:AST-057 edit carried over from the closed meta#8378 is dropped; the
  record is main's.

This branch was successfully deployed

1 active deployment
Preview — 0024510d 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. 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