Repository navigation
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
PR Analysis Report
The exact-head preview is not available yet. Generated by PR Enrichment workflow | View current CI run |
There was a problem hiding this comment.
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-100is current, or state the release-velocity rule here, and state FR34's drill-in in FR34 itself. - Say how a
useMenuPresscaller keeps the releasedlongPressDelayMsbehavior 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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 | |
There was a problem hiding this comment.
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 | |
There was a problem hiding this comment.
"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 |
There was a problem hiding this comment.
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 sendspointercancel. 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-zoomon 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:
- 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). - 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
usePressactivates 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?
|
#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.
|
|
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, 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:
The other seven ( 2. Registered roles that r2 does not list.
3. Component roles proposed as additions, each named 74 roles in 18 components
Equal values: An alternative to a hand-kept list: FR4 could cite the generated role table ( 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 |
There was a problem hiding this comment.
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]
| - **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 |
There was a problem hiding this comment.
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.
… 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.
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:
current, withapproved_byand its Decider lines naming the owner. Writing them is not approval: the owner's/approve-specat this exact head is.architecture:theme-tokensINV3). The bounce-back is not listed (OQ1).longPressDelayMskeeps a stated meaning.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 byspec:AST-100.Existing authority searched
component:SelectorDEC-1 (theadaptivequery);spec:AST-013FR3 (an Android Chrome support row);spec:AST-025FR14 (nested scrolling);spec:AST-057(Item's swipe constants and FR2);design:user-statesDR1/DR3;family:buttons;module:DropdownMenu/useMenuPressFR6.module:DropdownMenu/useMenuPressFR4;design:user-statesDR4;spec:AST-043FR1;spec:AST-012FR9 (compact);design:reduced-motion;architecture:public-component-apiINV14, INV16;spec:AST-017FR3, DEC-23;spec:AST-002FR3;architecture:react-component-runtimeINV5/INV7;family:overlay-dismissalAV6.Decision
design:reduced-motionDR2 and DR4.spec:AST-012FR9 compact definition where a presentation depends on size. The touch presentation applies after hydration.presentationchoice. Touch work costs nothing under a fine pointer.spec:AST-100.Rejected alternatives
presentationis the way back, INV16 and DEC-23).Claim scope and non-goals
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
Scope
architecture:knowledge-contractsINV18).Validation
pnpm check:knowledge: aligned.git diff --check: clean.