Skip to content

spec(AlertDialog): touch presentation, phone width, and action-group actions (FR6–FR9) - #8594

Merged
cixzhang merged 13 commits into
mainfrom
spec/alertdialog-touch-presentation
Oct 28, 2026
Merged

cixzhang merged 13 commits into
mainfrom
spec/alertdialog-touch-presentation

Conversation

@cixzhang

@cixzhang cixzhang commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Decided (Cindy, 2026-10-26: D30 = A, D31 = a, D32). FR7, FR8, and FR9 are decided; no open questions remain.

User impact

On a phone, AlertDialog today rises 16 px and grows from 95 % as it fades in, its dimming pops on, and it vanishes in one frame. A tap on an action during the entrance runs it. iOS's alert shrinks into place, holds touches while it appears, and fades out. This proposal records that touch presentation, as measured on the iOS 26.2 simulator in #8084, so the implementation has a record to follow.

Proposed requirements (draft component:AlertDialog)

  • FR6 — Motion under a finger.
    • Present: scale 1.2 → 1 with opacity and backdrop dimming 0 → 1 on springs.alertPresent, started at the browser's commit, with the larger start kept inside the viewport.
    • Dismiss: a fade alone on springs.alertDismiss; the dialog closes at its end.
    • Interruptions spring from where the alert is.
    • The roles are internal with UIKit's measured values (spec:AST-094 FR4, DEC-11), and become themeable once the public motion set exists. Reduce Motion gets the shared cross-fade (design:reduced-motion). A fine pointer keeps today's motion (spec:AST-094 FR19).
  • FR7 — Entrance input hold (DEC-1, D30). Pointer input is held while the alert enters, following spec:AST-094 FR35. Keyboard and assistive technology are never held, the entrance never makes content inert, and content is inert only during the dismissal fade.
  • FR8 — Action group (D32). AlertDialog's actions are a family:action-groups member. A press that slides across them runs the action it lifts on, and a lift outside runs nothing, under every pointer. Keyboard and AT are unchanged.
  • FR9 — Phone width (DEC-2, D31). On a phone (compact width, coarse pointer) the alert is 320 px wide in every theme, with no theme role. Desktop is unchanged.
  • PR2 — No added element. The motion plays on the dialog surface and its ::backdrop, within spec:AST-017 FR60's open-layer allowance.
  • The PR also adds verification rows for FR6–FR9.

Notes for review

Writing rubric results

…ut hold, action tracking, themed width (FR6–FR9, OQ1–OQ3)
@vercel

vercel Bot commented Oct 26, 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 28, 2026 9:48am UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Oct 28, 2026, 10:10 AM UTC

@github-actions github-actions Bot added the spec-auto-merge Auto-merge was enabled by the spec owner gate label Oct 26, 2026
@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

View Storybook for this PR · View Sandbox for this PR

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


Generated by PR Enrichment workflow | View current CI run

@cixzhang

Copy link
Copy Markdown
Contributor Author

Posted by Cindy's agent on her behalf.

Cindy's decision on taps during an entrance: every touch presentation absorbs taps and drags that land during its entrance. This covers alerts, sheets and dialogs, extending spec:AST-094 FR35 beyond menus and popovers. Only pointer input is held: keyboard and screen-reader activation are never held, and the content stays in the accessibility tree. Desktop is unchanged. The mechanism is the layer runtime's shared input hold (#8342). The record amendment follows in its own spec PR, and this PR then conforms to it.

@cixzhang

Copy link
Copy Markdown
Contributor Author

Posted by Cindy's agent on her behalf.

Cindy's decision on alert width: on phones (compact width with a touch pointer), alerts are 320 px wide in every theme, matching iOS, with no new theme role. Desktop is unchanged. The AlertDialog record states this, and the code conforms to it.

… on phones in every theme (DEC-2); OQ2 stays open
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 26, 2026
@cixzhang

Copy link
Copy Markdown
Contributor Author

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.

@vjeux vjeux left a comment

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.

Four places where this record differs from what has landed or been decided since its last push (23:34Z):

  • FR9 gives the width to a general compact-dialog theme role (400 px by default, 320 px from an iOS-style theme). The width decision on #8084 (23:32Z) is: on phones (compact width with a touch pointer), alerts are 320 px wide in every theme, with no new theme role; desktop is unchanged.
  • FR6 names the roles springs.alertPresent and springs.alertDismiss. Since f3e51b5 (#8084) they are alert.present and alert.dismiss, in the alert's own motion table.
  • FR8 tracks a finger across the actions under a touch presentation only. spec:AST-094 DEC-17 and family:action-groups FR5 (#8622) apply the slide to every pointer.
  • #8084 states the phone width as component:AlertDialog FR6 (b52c90c), which collides with this record's FR6.

@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 on the new draft requirements in component:AlertDialog: FR6 (touch motion), FR7 (entrance input hold), FR8 (action tracking), FR9 (compact-dialog width role) and PR2. That decision includes whether FR7 and FR8 should wait for the planned spec:AST-094 FR35 and FR26–FR28 amendments. An engineering owner records it by commenting /approve-spec 041b34add94af2cc9750c4fa08081ec226e92cdb, and the author may do so as an engineering owner. Whatever the decision, these fixes are needed before approval:

  • Restore PR1 under Performance and resources, which is now a literal \1.
  • Rewrite FR9, its verification row and OQ3 to the width decision posted on this pull request (320 px on phones in every theme, no new theme role), and say whether a caller's width prop still wins.
  • Give FR8 the presentation value that architecture:public-component-api INV16 requires for a touch input mode, and update the Compatibility lines that still say nothing changes.

[Automated review]

- **PR1 — Dialog-owned resources.** AlertDialog's only listener is the
small-screen media query behind the button order; Dialog owns the modal
lifecycle.
\1

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 line is a literal \1 where PR1 used to be ("AlertDialog's only listener is the small-screen media query behind the button order; Dialog owns the modal lifecycle"). Restore PR1. Restate it if FR7 or FR8 would add touch input handling, since its "only listener" sentence would then be false.

| FR6 | Under a touch presentation (`spec:AST-094` FR13), a modal AlertDialog presents by scaling from 1.2 to 1 with its opacity and its backdrop's dimming rising from 0 to 1 on the motion role `springs.alertPresent`, started at the browser's commit, with the larger start kept inside the viewport; it dismisses by a fade alone on `springs.alertDismiss` and closes at the fade's end; a close during the presentation, or a reopen during the dismissal, springs from where it is. The roles are internal with UIKit's measured values (`spec:AST-094` FR4, DEC-11). Under Reduce Motion both are the shared cross-fade (`design:reduced-motion` DR1–DR3). A fine primary pointer keeps today's motion (`spec:AST-094` FR19). | proposed from measured iOS 26.2 behavior | owner review |
| FR7 | Under a touch presentation, pointer input to the alert is held from the presentation's start until it can be read, through the layer runtime's shared input hold; keyboard and assistive-technology input are never held, and the entrance never makes the content inert. While the alert leaves, its content is inert for the dismissal fade. The hold's timing stays internal (`spec:AST-002` FR3). | proposed; pending owner decision (OQ1) | owner decision |
| FR8 | Under a touch presentation, a finger that slides from one action to another runs the action it lifts on, as a menu's press-tracking slide does (`spec:AST-094` FR26–FR28). | proposed; pending owner decision (OQ2) | owner decision |
| FR9 | The alert's width comes from a general compact-dialog width theme role, shared with Dialog's compact size and confirmation sheets, defaulting to today's 400 px; an iOS-style theme sets 320 px. AlertDialog does not set a touch width of its own. | proposed; pending owner decision (OQ3) | owner decision |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FR9 proposes a shared compact-dialog theme role (400 px by default, 320 px in an iOS-style theme). The width decision posted on this pull request is 320 px on phones in every theme, with no new theme role. FR9, its verification row and OQ3 should follow that decision and say whether a caller's width prop still wins.

| FR5 | `useImperativeAlertDialog` returns `show`, `hide`, and the element to render, and owns `isOpen`/`onOpenChange` for its caller. | shipped source and focused tests | verify |
| FR6 | Under a touch presentation (`spec:AST-094` FR13), a modal AlertDialog presents by scaling from 1.2 to 1 with its opacity and its backdrop's dimming rising from 0 to 1 on the motion role `springs.alertPresent`, started at the browser's commit, with the larger start kept inside the viewport; it dismisses by a fade alone on `springs.alertDismiss` and closes at the fade's end; a close during the presentation, or a reopen during the dismissal, springs from where it is. The roles are internal with UIKit's measured values (`spec:AST-094` FR4, DEC-11). Under Reduce Motion both are the shared cross-fade (`design:reduced-motion` DR1–DR3). A fine primary pointer keeps today's motion (`spec:AST-094` FR19). | proposed from measured iOS 26.2 behavior | owner review |
| FR7 | Under a touch presentation, pointer input to the alert is held from the presentation's start until it can be read, through the layer runtime's shared input hold; keyboard and assistive-technology input are never held, and the entrance never makes the content inert. While the alert leaves, its content is inert for the dismissal fade. The hold's timing stays internal (`spec:AST-002` FR3). | proposed; pending owner decision (OQ1) | owner decision |
| FR8 | Under a touch presentation, a finger that slides from one action to another runs the action it lifts on, as a menu's press-tracking slide does (`spec:AST-094` FR26–FR28). | proposed; pending owner decision (OQ2) | owner decision |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FR8 changes how the open alert follows a finger, so architecture:public-component-api INV16 asks for a presentation value that keeps the fine-pointer input mode. The record names none, and its Compatibility section still says the released default is preserved.

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec edabb48

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

Requesting changes: an engineering owner approved this head's direction, but the record still has a few issues to fix before it lands. FR8 cites family:action-groups and spec:AST-094 DEC-17, which only exist in #8622, and current spec:AST-094 FR11 still keeps a button's activation on the browser's click, so please land #8622 first and rebase, or move FR8 to a follow-up. Please also:

  • Rewrite the FR9 verification row, which still tests the theme role that FR9 and DEC-2 reject.
  • Fill in the FR8 verification row's states, failure expectation and audit section.
  • Update the Compatibility lines and Contract at a glance, and say in FR9 whether an explicit width still wins on a phone.
  • Point FR6's citations at current spec:AST-094 text (FR13 and the Non-goals instead of FR19, FR4 and DEC-15 instead of DEC-11), and drop FR7's mechanism clause.

[Automated review]

| FR5 | `useImperativeAlertDialog` returns `show`, `hide`, and the element to render, and owns `isOpen`/`onOpenChange` for its caller. | shipped source and focused tests | verify |
| FR6 | Under a touch presentation (`spec:AST-094` FR13), a modal AlertDialog presents by scaling from 1.2 to 1 with its opacity and its backdrop's dimming rising from 0 to 1 on the motion role `springs.alertPresent`, started at the browser's commit, with the larger start kept inside the viewport; it dismisses by a fade alone on `springs.alertDismiss` and closes at the fade's end; a close during the presentation, or a reopen during the dismissal, springs from where it is. The roles are internal with UIKit's measured values (`spec:AST-094` FR4, DEC-11). Under Reduce Motion both are the shared cross-fade (`design:reduced-motion` DR1–DR3). A fine primary pointer keeps today's motion (`spec:AST-094` FR19). | proposed from measured iOS 26.2 behavior | owner review |
| FR7 | Under a touch presentation, pointer input to the alert is held while it enters, through the layer runtime's shared input hold (`spec:AST-094` FR35); keyboard and assistive-technology input are never held, and the entrance never makes the content inert. While the alert leaves, its content is inert for the dismissal fade. The hold's timing stays internal (`spec:AST-002` FR3). | `spec:AST-094` FR35; owner decision (DEC-1) | decided |
| FR8 | AlertDialog's actions are an action group (`family:action-groups`): a press that slides across them runs the action it lifts on, and a lift outside them runs nothing, under every pointer; keyboard and assistive-technology behavior is unchanged. | `family:action-groups`; `spec:AST-094` DEC-17 | decided |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

family:action-groups and spec:AST-094 DEC-17 are not on main yet; both arrive with #8622. Meanwhile current spec:AST-094 FR11 keeps a button's activation on the browser's click, and FR26–FR28 cover only useMenuPress surfaces. Land #8622 first and rebase so these citations resolve, or move FR8 and its verification row to a follow-up. spec:AST-017 FR59 also asks the mouse change to carry its own owner decision, and this record's decision log has none for FR8.

| FR6, PR2 | Browser tests under a coarse pointer in Chromium and WebKit, a spring-curve oracle, a busy-main-thread case, and real-device clips on iOS Safari and Android Chrome | present, dismiss, close during present, reopen during dismiss, Reduce Motion, a fine pointer, a narrow viewport | a curve off its role, a start outside the viewport, a jump on interruption, a scale under Reduce Motion, changed motion under a fine pointer, or an added element fails | `audit:AlertDialog/behavior` |
| FR7 | Browser tests under a coarse pointer, with keyboard and assistive-technology input | a tap during the presentation, Enter and Escape during it, a screen reader's activation, a tap during the dismissal | a held key or assistive-technology action, content made inert during the entrance, or a tap that reaches a leaving alert fails | `audit:AlertDialog/behavior` |
| FR8 | Pointer-stream tests with a mouse, a pen, and a finger, and keyboard tests | | | |
| FR9 | Browser tests at a phone width and at a desktop width, in the default and an alternate theme | the default theme, an iOS-style theme, and Dialog's compact size reading the same role | a width other than the theme's fails | `audit:AlertDialog/behavior` |

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 row still tests the theme role that FR9 and DEC-2 reject ("an iOS-style theme, and Dialog's compact size reading the same role", "a width other than the theme's"). Suggest testing a phone width under a coarse pointer and a desktop width under a fine pointer, in the default and one alternate theme, and failing on any phone width other than 320 px in any theme or any change to the desktop width.

| FR1–FR5 | `AlertDialog.test.tsx` ("calls onOpenChange(false) on Escape", "does not call onOpenChange when action is clicked", "does not call onAction on Escape", "does not disable the cancel button while the action is loading", "exposes a named group instead, with no aria-modal") and `useImperativeAlertDialog.test.tsx` ("opens on show()", "closes on hide()") | open, loading, inline, imperative | Escape not cancelling, the action closing by itself, or the inline path claiming the role fails. | `audit:AlertDialog/behavior` |
| FR6, PR2 | Browser tests under a coarse pointer in Chromium and WebKit, a spring-curve oracle, a busy-main-thread case, and real-device clips on iOS Safari and Android Chrome | present, dismiss, close during present, reopen during dismiss, Reduce Motion, a fine pointer, a narrow viewport | a curve off its role, a start outside the viewport, a jump on interruption, a scale under Reduce Motion, changed motion under a fine pointer, or an added element fails | `audit:AlertDialog/behavior` |
| FR7 | Browser tests under a coarse pointer, with keyboard and assistive-technology input | a tap during the presentation, Enter and Escape during it, a screen reader's activation, a tap during the dismissal | a held key or assistive-technology action, content made inert during the entrance, or a tap that reaches a leaving alert fails | `audit:AlertDialog/behavior` |
| FR8 | Pointer-stream tests with a mouse, a pen, and a finger, and keyboard 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.

This row has no representative states, failure expectation or audit section, so nothing can fail it. Suggested states: a press on Cancel slid onto the action and lifted, and a press lifted outside the actions, each with a mouse, a pen and a finger, plus Enter and Space. Suggested failure: the press-start action running after a slide, any action running on a lift outside, or a changed keyboard or assistive-technology path. Audit section: audit:AlertDialog/behavior.

| FR3 | The anatomy is title, description, then the buttons: Cancel then action on wider screens, the action stacked above Cancel on small screens, Cancel keeping initial focus either way; the action may show a loading state while `isActionLoading`. | shipped source and focused tests | verify |
| FR4 | `isInline` renders the same anatomy without modality and without the alert-dialog role. | shipped source and public docs | verify |
| FR5 | `useImperativeAlertDialog` returns `show`, `hide`, and the element to render, and owns `isOpen`/`onOpenChange` for its caller. | shipped source and focused tests | verify |
| FR6 | Under a touch presentation (`spec:AST-094` FR13), a modal AlertDialog presents by scaling from 1.2 to 1 with its opacity and its backdrop's dimming rising from 0 to 1 on the motion role `springs.alertPresent`, started at the browser's commit, with the larger start kept inside the viewport; it dismisses by a fade alone on `springs.alertDismiss` and closes at the fade's end; a close during the presentation, or a reopen during the dismissal, springs from where it is. The roles are internal with UIKit's measured values (`spec:AST-094` FR4, DEC-11). Under Reduce Motion both are the shared cross-fade (`design:reduced-motion` DR1–DR3). A fine primary pointer keeps today's motion (`spec:AST-094` FR19). | proposed from measured iOS 26.2 behavior | owner review |

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 main, spec:AST-094 FR19 now says recognizers attach for every pointer, so it no longer backs "A fine primary pointer keeps today's motion"; FR13 and the Non-goals do. DEC-15 also supersedes DEC-11's "all internal", so the theme's tempo and character knobs reach these roles (FR4). Suggest citing FR13 and FR4 with DEC-15, updating system_specs, and aligning FR6's Draft review state with FR7–FR9.

| FR4 | `isInline` renders the same anatomy without modality and without the alert-dialog role. | shipped source and public docs | verify |
| FR5 | `useImperativeAlertDialog` returns `show`, `hide`, and the element to render, and owns `isOpen`/`onOpenChange` for its caller. | shipped source and focused tests | verify |
| FR6 | Under a touch presentation (`spec:AST-094` FR13), a modal AlertDialog presents by scaling from 1.2 to 1 with its opacity and its backdrop's dimming rising from 0 to 1 on the motion role `springs.alertPresent`, started at the browser's commit, with the larger start kept inside the viewport; it dismisses by a fade alone on `springs.alertDismiss` and closes at the fade's end; a close during the presentation, or a reopen during the dismissal, springs from where it is. The roles are internal with UIKit's measured values (`spec:AST-094` FR4, DEC-11). Under Reduce Motion both are the shared cross-fade (`design:reduced-motion` DR1–DR3). A fine primary pointer keeps today's motion (`spec:AST-094` FR19). | proposed from measured iOS 26.2 behavior | owner review |
| FR7 | Under a touch presentation, pointer input to the alert is held while it enters, through the layer runtime's shared input hold (`spec:AST-094` FR35); keyboard and assistive-technology input are never held, and the entrance never makes the content inert. While the alert leaves, its content is inert for the dismissal fade. The hold's timing stays internal (`spec:AST-002` FR3). | `spec:AST-094` FR35; owner decision (DEC-1) | decided |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No current architecture record or shipped code defines a layer-runtime input hold yet: architecture:layer-runtime INV14 lists none, and #8342 proposes it. architecture:knowledge-contracts INV18 keeps internal mechanisms out of component requirements. Suggest dropping "through the layer runtime's shared input hold", since spec:AST-094 FR35 already states the observable rule.

| FR6 | Under a touch presentation (`spec:AST-094` FR13), a modal AlertDialog presents by scaling from 1.2 to 1 with its opacity and its backdrop's dimming rising from 0 to 1 on the motion role `springs.alertPresent`, started at the browser's commit, with the larger start kept inside the viewport; it dismisses by a fade alone on `springs.alertDismiss` and closes at the fade's end; a close during the presentation, or a reopen during the dismissal, springs from where it is. The roles are internal with UIKit's measured values (`spec:AST-094` FR4, DEC-11). Under Reduce Motion both are the shared cross-fade (`design:reduced-motion` DR1–DR3). A fine primary pointer keeps today's motion (`spec:AST-094` FR19). | proposed from measured iOS 26.2 behavior | owner review |
| FR7 | Under a touch presentation, pointer input to the alert is held while it enters, through the layer runtime's shared input hold (`spec:AST-094` FR35); keyboard and assistive-technology input are never held, and the entrance never makes the content inert. While the alert leaves, its content is inert for the dismissal fade. The hold's timing stays internal (`spec:AST-002` FR3). | `spec:AST-094` FR35; owner decision (DEC-1) | decided |
| FR8 | AlertDialog's actions are an action group (`family:action-groups`): a press that slides across them runs the action it lifts on, and a lift outside them runs nothing, under every pointer; keyboard and assistive-technology behavior is unchanged. | `family:action-groups`; `spec:AST-094` DEC-17 | decided |
| FR9 | On a phone, a compact width under a coarse primary pointer, the alert is 320 px wide in every theme, as iOS's is; no theme role sets it. Elsewhere it keeps today's width. | owner decision (DEC-2) | decided |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Please say whether an explicit width (default 400) still wins on a phone, and whether the 320 px stays within Dialog's viewport clamp below about 352 px. The Compatibility lines (59–65) and Contract at a glance still say nothing changes for builders; they should describe FR8's and FR9's effect.

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec ed49cce

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

Requesting changes: the engineering-owner approval settles this record's direction, but FR8 still rests on family:action-groups and spec:AST-094 DEC-17, which only exist in #8622, while current spec:AST-094 FR11 keeps a button's activation on the browser's click. Please land #8622 first and rebase so FR8 cites current text (then add FR8's decision and restate PR1), or move FR8 and its verification row to a follow-up. While you're there, mark FR6 as decided and list the touch presentation under the record's Ownership boundary.

[Automated review]

| FR5 | `useImperativeAlertDialog` returns `show`, `hide`, and the element to render, and owns `isOpen`/`onOpenChange` for its caller. | shipped source and focused tests | verify |
| FR6 | Under a touch presentation (`spec:AST-094` FR13), a modal AlertDialog presents by scaling from 1.2 to 1 with its opacity and its backdrop's dimming rising from 0 to 1 on the motion role `springs.alertPresent`, started at the browser's commit, with the larger start kept inside the viewport; it dismisses by a fade alone on `springs.alertDismiss` and closes at the fade's end; a close during the presentation, or a reopen during the dismissal, springs from where it is. The roles are internal, with UIKit's measured values at `tempo` 1 and `'standard'`, and follow the theme's motion knobs (`spec:AST-094` FR4, DEC-15). Under Reduce Motion both are the shared cross-fade (`design:reduced-motion` DR1–DR3). A fine primary pointer keeps today's motion (`spec:AST-094` FR13 and its Non-goals). | proposed from measured iOS 26.2 behavior | owner review |
| FR7 | Under a touch presentation, pointer input to the alert is held while it enters (`spec:AST-094` FR35); keyboard and assistive-technology input are never held, and the entrance never makes the content inert. While the alert leaves, its content is inert for the dismissal fade. The hold's timing stays internal (`spec:AST-002` FR3). | `spec:AST-094` FR35; owner decision (DEC-1) | decided |
| FR8 | AlertDialog's actions are an action group (`family:action-groups`): a press that slides across them runs the action it lifts on, and a lift outside them runs nothing, under every pointer; keyboard and assistive-technology behavior is unchanged. | `family:action-groups`; `spec:AST-094` DEC-17 | decided |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FR8's Basis cites family:action-groups and spec:AST-094 DEC-17, which are not on main yet (both arrive with #8622). Current spec:AST-094 FR11 says a control's "activation stays the browser's click", and FR26–FR28 cover only useMenuPress surfaces, so FR8 contradicts current text. Land #8622 first and rebase (then add FR8's own decision and restate PR1, whose "only listener" sentence FR8's press tracking makes untrue), or move FR8 and its verification row to a follow-up.

| FR3 | The anatomy is title, description, then the buttons: Cancel then action on wider screens, the action stacked above Cancel on small screens, Cancel keeping initial focus either way; the action may show a loading state while `isActionLoading`. | shipped source and focused tests | verify |
| FR4 | `isInline` renders the same anatomy without modality and without the alert-dialog role. | shipped source and public docs | verify |
| FR5 | `useImperativeAlertDialog` returns `show`, `hide`, and the element to render, and owns `isOpen`/`onOpenChange` for its caller. | shipped source and focused tests | verify |
| FR6 | Under a touch presentation (`spec:AST-094` FR13), a modal AlertDialog presents by scaling from 1.2 to 1 with its opacity and its backdrop's dimming rising from 0 to 1 on the motion role `springs.alertPresent`, started at the browser's commit, with the larger start kept inside the viewport; it dismisses by a fade alone on `springs.alertDismiss` and closes at the fade's end; a close during the presentation, or a reopen during the dismissal, springs from where it is. The roles are internal, with UIKit's measured values at `tempo` 1 and `'standard'`, and follow the theme's motion knobs (`spec:AST-094` FR4, DEC-15). Under Reduce Motion both are the shared cross-fade (`design:reduced-motion` DR1–DR3). A fine primary pointer keeps today's motion (`spec:AST-094` FR13 and its Non-goals). | proposed from measured iOS 26.2 behavior | owner review |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FR6's Draft review state still reads "owner review", although this head is owner-approved and FR7–FR9 read "decided".

| FR6 | Under a touch presentation (`spec:AST-094` FR13), a modal AlertDialog presents by scaling from 1.2 to 1 with its opacity and its backdrop's dimming rising from 0 to 1 on the motion role `springs.alertPresent`, started at the browser's commit, with the larger start kept inside the viewport; it dismisses by a fade alone on `springs.alertDismiss` and closes at the fade's end; a close during the presentation, or a reopen during the dismissal, springs from where it is. The roles are internal, with UIKit's measured values at `tempo` 1 and `'standard'`, and follow the theme's motion knobs (`spec:AST-094` FR4, DEC-15). Under Reduce Motion both are the shared cross-fade (`design:reduced-motion` DR1–DR3). A fine primary pointer keeps today's motion (`spec:AST-094` FR13 and its Non-goals). | proposed from measured iOS 26.2 behavior | owner review |
| FR7 | Under a touch presentation, pointer input to the alert is held while it enters (`spec:AST-094` FR35); keyboard and assistive-technology input are never held, and the entrance never makes the content inert. While the alert leaves, its content is inert for the dismissal fade. The hold's timing stays internal (`spec:AST-002` FR3). | `spec:AST-094` FR35; owner decision (DEC-1) | decided |
| FR8 | AlertDialog's actions are an action group (`family:action-groups`): a press that slides across them runs the action it lifts on, and a lift outside them runs nothing, under every pointer; keyboard and assistive-technology behavior is unchanged. | `family:action-groups`; `spec:AST-094` DEC-17 | decided |
| FR9 | On a phone, a compact width under a coarse primary pointer, the alert is 320 px wide in every theme, as iOS's is; no theme role sets it. A caller's explicit `width` still wins there (`architecture:public-component-api` INV5), and Dialog's viewport clamp still applies on a screen narrower than the alert. Elsewhere it keeps today's width. | owner decision (DEC-2) | decided |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"a compact width" could be read as width alone; the shared definition is compact, width below md or height below 500 (spec:AST-012 FR9, architecture:public-component-api INV17). Citing it keeps a landscape phone unambiguous.

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 2fbd091

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

Requesting changes: an engineering owner approved this head's direction, and FR8 now rests on the current family:action-groups record and spec:AST-094 DEC-17, so one small record fix remains. Please update the Contract at a glance so End-user impact names what FR6–FR9 change for people (the phone motion and entrance hold, the 320 px phone width, and the slide that now runs the lifted action under every pointer, mouse included), and Governing rules lists spec:AST-094, family:action-groups, design:reduced-motion and spec:AST-017. While you're there, consider saying where focus sits during the dismissal fade, restoring None. under Open questions, and using the template's settled state.

[Automated review]

@@ -39,8 +46,8 @@ system_specs: [spec:AST-020/FR15]
| Public contract | None changes. `AlertDialog`, `AlertDialogProps`, and `useImperativeAlertDialog` are exported from `@astryxdesign/core/AlertDialog`; `title`, `description`, `actionLabel`, `onAction`, and `onOpenChange` are required with `isOpen`, plus cancel label, action variant, loading, width, and an inline preview mode. |
| Behavior | A modal confirmation that interrupts: it opens through the native dialog, cannot be dismissed by a click outside, closes on Escape or Cancel through `onOpenChange(false)`, and runs `onAction` on the action button. |
| End-user impact | A keyboard or screen-reader user is told a decision is needed (the title and description are announced), lands on Cancel, and cannot lose the dialog by clicking past 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 row still describes only the keyboard and screen-reader baseline, while FR6–FR9 change what people meet: the phone motion and entrance hold, the 320 px phone width, and FR8's slide, which lets a mouse press that starts on Cancel run the destructive action. The template keeps Builder impact for migration and caller choices, so FR8's effect belongs here; Governing rules (line 52) should also list spec:AST-094, family:action-groups, design:reduced-motion and spec:AST-017.

| FR4 | `isInline` renders the same anatomy without modality and without the alert-dialog role. | shipped source and public docs | verify |
| FR5 | `useImperativeAlertDialog` returns `show`, `hide`, and the element to render, and owns `isOpen`/`onOpenChange` for its caller. | shipped source and focused tests | verify |
| FR6 | Under a touch presentation (`spec:AST-094` FR13), a modal AlertDialog presents by scaling from 1.2 to 1 with its opacity and its backdrop's dimming rising from 0 to 1 on the motion role `springs.alertPresent`, started at the browser's commit, with the larger start kept inside the viewport; it dismisses by a fade alone on `springs.alertDismiss` and closes at the fade's end; a close during the presentation, or a reopen during the dismissal, springs from where it is. The roles are internal, with UIKit's measured values at `tempo` 1 and `'standard'`, and follow the theme's motion knobs (`spec:AST-094` FR4, DEC-15). Under Reduce Motion both are the shared cross-fade (`design:reduced-motion` DR1–DR3). A fine primary pointer keeps today's motion (`spec:AST-094` FR13 and its Non-goals). | proposed from measured iOS 26.2 behavior | decided |
| FR7 | Under a touch presentation, pointer input to the alert is held while it enters (`spec:AST-094` FR35); keyboard and assistive-technology input are never held, and the entrance never makes the content inert. While the alert leaves, its content is inert for the dismissal fade. The hold's timing stays internal (`spec:AST-002` FR3). | `spec:AST-094` FR35; owner decision (DEC-1) | decided |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Non-blocking: with the content inert for the fade and the dialog closing only at its end, focus leaves the pressed button for about 0.4 s before Dialog returns it to the opener. Consider stating where focus sits during the fade.

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 8d58038

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

Requesting changes: an engineering owner approved this revision's direction, and FR6–FR9 match the current touch-presentation, action-groups, reduced-motion and release records, so one small record fix remains. Under Ownership boundary, please move "its actions' membership in family:action-groups (FR8)" from Owns to Does not own (for example "A press that slides across the actions acts on the lifted one, as family:action-groups owns"), because that family record owns membership and the shared slide behavior, as ButtonGroup, Toolbar and Banner already record. While you're there, consider adding a fine-pointer state to the FR7 and FR9 verification rows, saying where focus sits during the fade-out, using the template's settled review state, and refreshing the title, which still mentions OQ1–OQ3.

[Automated review]

Comment on lines +87 to +88
- The touch presentation's motion, input hold, and phone width (FR6, FR7, FR9),
and its actions' membership in `family:action-groups` (FR8).

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 clause claims an ownership family:action-groups holds: its Content boundary says "This file owns action-group membership and the shared press, slide, and lift behavior", and architecture:knowledge-contracts INV2 reads "One fact has one owner". ButtonGroup, Toolbar and Banner list the same thing under Does not own. Suggest dropping it here and adding under Does not own: "A press that slides across the actions acts on the lifted one, as family:action-groups owns (FR8)."

Comment on lines +213 to +215
| FR7 | Browser tests under a coarse pointer, with keyboard and assistive-technology input | a tap during the presentation, Enter and Escape during it, a screen reader's activation, a tap during the dismissal | a held key or assistive-technology action, content made inert during the entrance, or a tap that reaches a leaving alert fails | `audit:AlertDialog/behavior` |
| FR8 | Pointer-stream tests with a mouse, a pen, and a finger, and keyboard tests | a press on Cancel slid onto the action and lifted, and a press lifted outside the actions, each with a mouse, a pen, and a finger; Enter and Space on each action | the press-start action running after a slide, any action running on a lift outside, or a changed keyboard or assistive-technology activation fails | `audit:AlertDialog/behavior` |
| FR9 | Browser tests at a phone width and at a desktop width, in the default and an alternate theme | a phone width and a desktop width, in the default and an alternate theme; a caller `width` at a phone width; a screen narrower than 320 px | a phone-width alert other than 320 px in any theme without a caller `width`, a caller `width` overridden, a desktop width changed, or an alert wider than the viewport fails | `audit:AlertDialog/behavior` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Non-blocking: neither the FR7 nor the FR9 row has a fine-pointer state. The FR9 row passes an alert keyed on viewport width alone (no narrow fine-pointer window, no coarse screen that is not compact), and the FR7 row passes a hold that also swallows a mouse click, although spec:AST-094 FR35 leaves a mouse unchanged. Adding those states lets the rows fail a pointer-blind implementation.

@cixzhang cixzhang changed the title spec(AlertDialog): proposed — touch presentation (FR6–FR9; owner calls OQ1–OQ3) spec(AlertDialog): touch presentation, phone width, and action-group actions (FR6–FR9) Oct 28, 2026
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 67f1023

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

Approving: an engineering owner approved this revision's direction, FR6–FR9 match the current touch-presentation, action-groups, reduced-motion and compatibility records, and the requested ownership fix is in. Whichever of this pull request and #8084 lands second should reconcile #8084's own FR6 width row and its presentation prop with this record. Two optional touches: say in FR7 where focus sits during the dismissal fade, and use the template's settled review state instead of decided.

[Automated review]

@cixzhang
cixzhang merged commit a264f63 into main Oct 28, 2026
59 checks passed
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 28, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 28, 2026
Her merged record (meta#8594) keeps AlertDialog's public contract unchanged:
the touch presentation is a behavior change, and on a phone a caller's
width is its way back (FR9). Pin AlertDialogProps so it has no
presentation prop and keeps width. Fails typecheck while the prop exists.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 28, 2026
…keeps the public contract

Her merged AlertDialog record (meta#8594) changes behavior under a touch
presentation without changing the public API: a caller's width is the
way back on a phone (FR9), and no other prop is added. Remove the
presentation prop, its AlertDialogPresentation type, the
DialogPresentation story, its doc entry and the tests that used it. The
width test also covers a caller's width on a phone.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 28, 2026
…lertDismiss

Her merged AlertDialog FR6 (meta#8594) names the alert's motion roles
springs.alertPresent and springs.alertDismiss, the springs table's rows.
Expect those role ids, the alert's two springs among the registered
springs roles, and AlertDialog registering those two rows alone. Fails
while the roles are registered as alert.present and alert.dismiss.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 28, 2026
…d springs.alertDismiss

Her merged AlertDialog FR6 (meta#8594) names the alert's motion roles by
their springs table rows. Register the alert's two springs among the
springs roles under those names, rename AST-094 FR4's two rows to match,
and let AlertDialog register those two rows alone (registerAlertSprings),
so its bundle still carries no other spring. The alert table keeps only
its measured values. Same springs and timings; only the role ids change.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 28, 2026
…he keyboard keeps the buttons

Her AlertDialog record (meta#8594) verifies FR8 with a press lifted outside
the actions under a mouse, a pen and a finger, and with Enter and Space on
each action. Add the two cases the tests lacked: a mouse press that slides
off both actions highlights nothing and runs nothing at the lift, and under
a coarse and a fine pointer Enter runs the action and Space cancels. A
mutant that stops tracking a mouse's moves fails the first; one that drops
the buttons' own click fails the second.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 30, 2026
meta#8084 removed AlertDialog's presentation prop to keep her record's public
contract (meta#8594: no public API change; a caller's width is the phone width's
way back). The compact row's case for that prop and the changeset's mention
of it go with it; a mouse still keeps today's stack.
@github-actions
github-actions Bot deleted the spec/alertdialog-touch-presentation branch October 31, 2026 06:09

This branch was successfully deployed

1 active deployment
Preview — 67f10235 Deployed Oct 28, 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.

2 participants