Skip to content

refactor(core): AlertDialog's touch tracking runs on the shared action-group tracker - #8110

Draft
vjeux wants to merge 1403 commits into
meta:mainfrom
vjeux:uikit/c19-action-group-tracker
Draft

vjeux wants to merge 1403 commits into
meta:mainfrom
vjeux:uikit/c19-action-group-tracker

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #8084, #8346, #8360. Review only this PR's change: vjeux/astryx@24627e8...b0106ad

Maintainer impact

useActionGroupTracking (#8083) tracks a group of actions the way UIKit does, but only for touches that begin on the element it is attached to: the gesture host (#8072) gives a recognizer only the touches that land inside it. Menus need the same tracking for a finger that went down elsewhere: the finger holding a DropdownMenu trigger slides into the menu that opened under it (UIMenu's handoff), and a context menu's long press continues into its menu. Without a shared core, each would re-implement the rect cache, paint and activation.

Intended invariant

AlertDialog's tracking is unchanged, as #8083 and #8084 state it:

  • rects are read once when a press begins, with no render and no layout read per move;
  • the highlight is cut on leave and faded for PRESS_FADE_MS at the lift;
  • disabled actions are skipped;
  • tracking turned off mid-touch drops the held highlight and its lift runs nothing (fix(AlertDialog): a finger that slides between the actions runs the one it lifts on #8083);
  • the action runs through its own click() inside the lift's event;
  • a press isInert() calls inert at begin is taken but paints and runs nothing;
  • the in-scroller rule (content-touch delay within the press slop) is unchanged.

Change

The tracking moves from the hook into actionGroupTracker.ts: createActionGroupTracker(getOptions) returns begin(x, y, {inert}), move, end, cancel and offsetBy(dx, dy) (an autoscroll shifts the cached rects without reading layout) and dispose() (pending fades finish on unmount). Its options are several roots, onHighlightChange, and paintsHighlight: false for owners that show the highlight another way (a menu moves focus). useActionGroupTracking binds the tracker to its one long press exactly as before. A recognizer elsewhere drives the same object and must call end from its touchend.

Evidence

  • Before: the tracking lives inside the hook's recognizer callback, so another recognizer can't drive it.
  • After: useActionGroupTracking.test.tsx passes unchanged; AlertDialog's suites pass unchanged; 13 new tracker tests drive it directly, including a press that begins while another is tracked.
  • Behavior unchanged: same attribute writes, same activation path, same inert handling.

Scope

  • No intended public API, product behavior, visual direction, or policy change.
  • Product changes discovered during the work were removed or split.
  • Public text and artifacts contain no internal Meta context.

Testing

  • pnpm vitest run hooks, AlertDialog, ActionSheet, DropdownMenu, motion, gestures and utils at this head: 2435 passed, including the tracker's tests; useActionGroupTracking.test.tsx and AlertDialog's suites unchanged. Tracking turned off mid-touch runs the tracker's cancel (the held highlight drops, the lift runs nothing) and then dispose (pending fades end).
  • AlertDialog's a11y contract (Chromium) 3/3; its interaction proofs: Chromium 25/25, WebKit (Playwright image) 21 + 4 Chromium-only skips.
  • pnpm build and pnpm typecheck; CI=true pnpm lint with no error; CLI experimental isolation and golden reads 47/47; check:knowledge, check:changesets.

Stack (merge in this order): #8083 → #8084, #8346, and F2's line to #8360 → this PR.

@vercel

vercel Bot commented Oct 24, 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 26, 2026 11:09pm UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Oct 31, 2026, 3:42 AM UTC

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 24, 2026
@vjeux
vjeux force-pushed the uikit/c19-action-group-tracker branch 2 times, most recently from 3bd3483 to 190b6a1 Compare October 24, 2026 05:47
@vjeux
vjeux force-pushed the uikit/c19-action-group-tracker branch from 190b6a1 to 7f6b9ab Compare October 24, 2026 06:18

@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 waits on an engineering owner's decision about the draft spec:AST-094 (touch presentations follow UIKit): its FR1–FR23 and DEC-1/DEC-2 are new requirements that no current record states, yet they are the only authority for this branch's AlertDialog touch tracking and motion and for the new 744px/500px bottom-sheet rule, so an engineering owner needs to approve or reject them by commenting /approve-spec <full head SHA> on this pull request (the author, as an engineering owner, may do so). That decision also needs to cover the record's proposed amendments to architecture:theme-tokens INV3, design:user-states DR1 and DR3, the family:buttons press treatment and spec:AST-025 FR14. Separately, because the PR targets main, its diff is the whole 26-commit stack (about 27,000 lines in 144 files) rather than the four-file tracker refactor the description covers; please retarget it onto #8084's branch or rebase it once the stack has landed. The tracker extraction itself reads as behavior-preserving.

[Automated review]

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

Current run: The current CI run concluded cancelled. Current analysis is unavailable.

View Storybook for this PR · View Sandbox for this PR


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.

Approval waits on an engineering owner's decision about the draft spec:AST-094 (touch presentations follow UIKit): its FR1–FR23 and DEC-1/DEC-2 are new requirements that no current record states, yet they are the only authority for this branch's AlertDialog touch tracking and motion and for the new 744px/500px sheet rule, which also departs from Selector's current DEC-1 ("adaptive keeps its width-and-pointer query"). An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request (the author may do so as an engineering owner), and the description needs the ## Writing rubric results for the record that the review gate reads such an approval against. Separately, because the PR targets main, its diff is the whole 28-commit stack (about 28,000 lines in 145 files) rather than the four-file tracker refactor the description covers, so please retarget it onto #8084's branch or rebase it once the stack has landed. Seven [chore] changesets (for example gesture-host.md and uikit-motion-animator.md) still say no component uses code that AlertDialog now runs, so please reword them before they publish.

[Automated review]

Comment on lines +59 to +60
export const COMPACT_TOUCH_QUERY =
'(width < 744px) and (pointer: coarse), (height < 500px) and (pointer: coarse)';

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 query changes what presentation="adaptive" resolves to for DropdownMenu, ContextMenu, MoreMenu, Selector and MultiSelector, and where DropdownMenu and ContextMenu sub-menus drill in (useMenuDrillIn.tsx reads the same constant): a phone in landscape now gets the sheet, and a 744 to 768 px wide touch screen the anchored popover. Selector's current record says in DEC-1 that adaptive keeps its width-and-pointer query, so this rule needs an engineering owner's decision alongside the draft AST-094 FR13.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That query belongs to the touch presentation resolver (#8062); the decision is with its owners.

Comment thread .changeset/gesture-host.md Outdated
'@astryxdesign/core': patch
---

[chore] Internal: the gesture system's DOM host (claims, click suppression, the browser's takeover, the pointer behind a contact). Not exported from the package entry point; no component uses it yet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

At this head AlertDialog runs the gesture host (through useGestureRecognizer), so "no component uses it yet" will be false in the release that ships alertdialog-touch-action-tracking.md. The same sentence appears in gesture-recognizer-core.md, gesture-tap-long-press.md, gesture-browser-negotiation.md, uikit-motion-animator.md and uikit-motion-math.md, and as "no component reads it yet" in uikit-constants-table.md; dropping it, or naming AlertDialog as the first consumer, keeps the changelog accurate.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Those [chore] changesets belong to the foundation PRs; the wording is with their owners.

@cixzhang

Copy link
Copy Markdown
Contributor

Posted by Cindy's agent on her behalf.

Parts of this PR re-implement behavior that already exists, or is being specified, as a shared part of core. Converging keeps one implementation per behavior:

This is about where the behavior lives, not about the PR's intent.

@vjeux

vjeux commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Agreed: one implementation.

@vjeux
vjeux force-pushed the uikit/c19-action-group-tracker branch from 230fe23 to 70c9abd Compare October 25, 2026 01:56
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
# Conflicts:
#	packages/core/src/motion/uikit/index.ts
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
# Conflicts:
#	packages/core/src/motion/uikit/index.ts
@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Oct 25, 2026
cixzhang and others added 9 commits October 26, 2026 14:29
A watcher already adds nothing to the derived touch-action, and
manageStyle is per element, so attaching one with manageStyle: false
beside a claiming recognizer switched off that recognizer's touch-action
(spec:AST-094 FR18). The README now says a watcher adds nothing instead.

Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
meta#8104's dependencies, meta#8089, meta#7839 and meta#8110, in one commit, so its compare link shows only its own change.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
The DropdownMenu record is the dependencies' own combination; this pull request's own change does not edit it.
cixzhang and others added 7 commits October 26, 2026 14:51
…n roles springs.alertPresent and springs.alertDismiss

spec:AST-094 FR4 has every spring a touch presentation plays be a motion
role; the alert played its springs straight from the table. They are now
registered as internal roles (DEC-11: no theme surface), named in FR4's role
table with their measured values, and useAlertMotion plays them through the
roles after registering the springs table. A new test checks the roles the
alert plays and fails on the old constants.

Co-authored-by: Christopher Chedeau <vjeux@users.noreply.github.com>
…acking turns off or the group unmounts

The case "leaves no fade behind when tracking turns off or the group
unmounts" asserted that no timer at all was left. F2's shared click guard on
meta#8360 arms a window at each tracked release, and that window must outlive
the closing alert so the lift's click is still swallowed; in a tree that
carries it the case failed with one timer left (C19 folding meta#8110), though
the fade itself was gone.

Each check now counts the timers that go: the lift starts the fade's timer,
and turning tracking off, or unmounting, removes exactly that one. A leaked
fade still fails it (checked by removing the fade's clearTimeout: 1 timer
left where none should be).

Verified on this head and on a scratch merge with meta#8360 559ab88, where the
old case fails (expected 1 to be 0) and this one passes, 21 of 21.
… and name the old and new defaults in the changeset

- The alert's springs are the roles alert.present and alert.dismiss, in the
  alert's own motion table (motion/uikit/alert.ts), so registering them
  carries those two rows and no other spring (it registered the whole springs
  table before, +596 B gzip; architecture:component-style-authoring INV6).
  FR4's role table names them; a bundle test checks the rows.
- The changeset names the defaults under a finger: the width goes from 400 to
  320 px, and the motion replaces the Dialog's entrance (rising 16 px and
  scaling from 0.95 while it fades in) and exit (spec:AST-017 FR3).
- useAlertMotion's header says its springs are internal roles.

Co-authored-by: Christopher Chedeau <vjeux@users.noreply.github.com>
…, citing the animator's iOS Safari known limit

Co-authored-by: Christopher Chedeau <vjeux@users.noreply.github.com>
…8084)

meta#8110's dependencies in one commit, so its compare link shows only its own change: meta#8084, meta#8346, and F2's line to meta#8360, which carries the gesture host's and press tracker's per-group UIKit rows.
meta#8084 now carries meta#8083's test that counts only the fade's timer and registers only the alert's own two springs, so F2's line to meta#8360 can come in with its click guard: it brings the gesture host's and press tracker's per-group UIKit rows.
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Oct 26, 2026
@cixzhang

Copy link
Copy Markdown
Contributor

Posted by Cindy's agent on her behalf.

Cindy's decision on sliding between actions: a group of actions behaves like a menu under a press. Press an action, slide across its sibling actions with the highlight following, and the action you lift on runs. Sliding off the group cancels. One shared module, the existing action-group tracker, governs this for menus, SegmentedControl, AlertDialog's actions, Dialog's action/footer area (not arbitrary dialog content), ButtonGroup and ToggleButtonGroup. In a toggle group, only the item you lift on toggles; sliding across never toggles the items in between. Keyboard and screen-reader behavior is unchanged. The record amendment to spec:AST-094 FR26–FR28 follows in its own spec PR, and each component then adopts the shared tracker in its own PR.

@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 waits on an engineering owner's decision: this head adds alert.present and alert.dismiss to spec:AST-094 FR4, which the record on main does not list, so an engineering owner needs to approve those two rows by commenting /approve-spec <full head SHA> on this pull request (the author may do so as an engineering owner), or have them named in AlertDialog's own record instead. Whatever that decision, these changes are still needed:

  • Land #8084, #8346 and the #8360 gesture line first (or retarget onto the parent branch), so this pull request is its 182-line refactor rather than about 34,800 lines against main.
  • Remove the Lab core-internal module resolver, or move it to the @astryxdesign/core/internal/touch subpath that main's react-component-runtime INV13 now prescribes.
  • Correct gesture-browser-negotiation.md and gesture-press-tracking.md, which say no component uses code that AlertDialog now runs.
  • Have the action-group tracker paint through paintPress, which this diff declares the press attribute's only writer.

[Automated review]

| `springs.menuClose` | damping ratio 0.85, response 0.35 s | measured: UIMenu closing, iOS 26.2 simulator, 2026-10-23 |
| `springs.subMenuOpen` | damping ratio 0.85, response 0.4 s | measured: UIKit's own spring for UIMenu's sub-menu opening, iOS 26.2 simulator, 2026-10-24 |
| `springs.subMenuBack` | damping ratio 1, response 0.4 s | measured: UIKit's own spring for UIMenu's sub-menu header back, iOS 26.2 simulator, 2026-10-24 |
| `alert.present` | mass 1, stiffness 522.35, damping 45.71 | measured: UIAlertController alert appearing, iOS 26.2 simulator, 2026-10-23 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

These two rows add AlertDialog's springs to FR4's role table, which AST-094 on main does not list; FR4 and DEC-5 there have a component's own measured roles join through the generated role table from that component's own record. They need an engineering owner's decision (/approve-spec <full head SHA>), or a move to AlertDialog's record.

const path = require('node:path');

/** The internal module paths Lab imports: motion, gestures, the touch resolver. */
const CORE_INTERNAL_SPECIFIER =

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

main's react-component-runtime INV13 now says Core and Lab reach the touch modules through @astryxdesign/core/internal/touch. Resolving @astryxdesign/core/motion/*, gestures/* and utils/touchPresentation here (and in Lab's tsconfig paths) takes a different route, and no Lab source imports these paths yet, so this could go until the first Lab import needs the subpath.

return;
}
clearFade(element);
element.setAttribute(PRESSED_ATTRIBUTE, 'on');

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 diff makes writePress/paintPress in utils/pressFeedback.ts the one writer of data-astryx-press (it keeps the fade clock and respects a held paint), but the tracker writes the attribute and runs its own fade timers here, at :251, :258-266 and in dispose. Painting through paintPress would keep a menu row's held paint intact once menus adopt the tracker.

@vjeux

vjeux commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #8083, which now makes useActionGroupTracking the long-press adapter over the shared tracker; nothing here remains but its changeset. Closing is yours. For #8195: when it folds the new #8084, the one-file conflict in useActionGroupTracking.ts resolves to #8083's version.

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
meta#8220 keeps a phone menu beside its trigger (the engineering owner's decision), so the action sheet card's imports and exports no longer sit beside the covering placement's, and DropdownMenu imports the touch presentation read for the card itself (the covering placement imported it before). meta#8373 replaces meta#8110 as the base for the card's tracking and input hold.
@cixzhang cixzhang closed this Oct 28, 2026
@cixzhang cixzhang reopened this Oct 28, 2026
@cixzhang

Copy link
Copy Markdown
Contributor

Posted by Cindy's agent on her behalf: closed and reopened only to restart this PR's CI on the current workflow after tonight's queue-wide cancel; nothing in the PR changed.

@vjeux
vjeux marked this pull request as draft October 29, 2026 22:31

This branch was successfully deployed

1 active deployment
Preview — b0106ad6 Deployed Oct 26, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review needs:spec-owner-review Current knowledge records await owner approval

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants