Skip to content

spec(family): action groups — press, slide across, act on lift (AST-094 FR26–FR28) - #8622

Merged
cixzhang merged 38 commits into
mainfrom
spec/ast094-action-groups
Oct 28, 2026
Merged

cixzhang merged 38 commits into
mainfrom
spec/ast094-action-groups

Conversation

@cixzhang

@cixzhang cixzhang commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Decided (Cindy, 2026-10-26: D32, then the full scope: "capture all potentially related components so they get the behavior consistently").

User impact

A person who presses one action and slides to another gets the same result in a menu, an alert's buttons, a button group, or a toolbar. The action they lift on runs, sliding off runs nothing, and in a toggle group only the lifted item toggles. Keyboard and screen-reader users are unchanged.

Records

  • New family:action-groups (docs/families/action-groups.md):
    • FR1 the lift decides; FR2 the highlight follows; FR3 sliding off cancels (the spec:AST-094 FR26–FR28 rule); FR4 a toggle group toggles only the lifted action.
    • FR5: every web pointer (Cindy 15:39/15:40). Under a fine pointer a group keeps one pointerdown listener on its element, cancelable where a member needs it, and adds document listeners only during a press. The 44 px target is each member's hit expansion under a coarse pointer only. Keyboard and AT are unchanged.
    • Shared owners: the internal actionGroupTracker for every member but menus and Selector's and MultiSelector's options, which stay on useMenuPress; when they converge is open (INV13, OQ1).
    • Members: menus and sub-menus, including the navigation menus of TopNav, SideNavHeading, TopNavHeading and NavHeadingMenu, BreadcrumbItem's menu, and TabMenu; Selector and MultiSelector options and the date wheels; AlertDialog's actions; ButtonGroup; ToggleButtonGroup; DateRangeInput's presets; Toolbar's buttons (fields skipped); and Banner and Toast action rows with two or more actions. Action rows in Dialog footers, Card footers, or any LayoutFooter get the behavior by composing ButtonGroup.
    • Exclusions, with reasons: text inputs and InputGroup as a whole; TabList; Pagination and Stepper; RadioList, CheckboxList, and SelectableCard; Token chips; the suggestion lists of Typeahead/Tokenizer, CommandPalette and the chat composer; TopNavMegaMenu's panel; SideNavItem's collapsed-rail flyout; Popover itself; Calendar's day grid.
    • Joining by rule when built: action sheet, tab bar or navigation bar, filter-chip group, rating.
  • Exception: SegmentedControl, whose thumb follows the finger and moves the selection, is governed by its own record (Cindy, 16:38).
  • spec:AST-094: FR26–FR28 become the family's shared rule. FR19 lets a member track its own group as FR24 allows (one element listener and press-time document listeners), and FR24 counts its release-armed click swallower as press work, and DEC-17 records the decision. Its number assumes DEC-15 (spec(AST-108): theme motion is three knobs — tempo, character, reducedMotion #8613) and DEC-16 (spec(AST-094): every touch presentation holds pointer input while it enters (FR35) #8618).

pnpm check:knowledge passes.

Writing rubric results

  • R1 present tense: pass. R2 research: none.
  • R3 verification: the family's verification map.
  • R4 rejected alternatives: one line in DEC-17. R5 change references: none in the records. R6 open questions: none.

@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 12:00am UTC

Request Review

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec d88563a

@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, 12:18 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
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 99d6101

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 3369b14

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec f4f87d2

@github-actions

Copy link
Copy Markdown
Contributor

That /approve-spec comment did not change spec-owner-approval: it named 3369b14, which is not the current head.

The gate only accepts the exact current head, so post this instead:

/approve-spec f4f87d28e5da8e83ef13110e52b52c7f40a2d03a

A new commit invalidates the command, so repeat it after any push.

…one passive listener (FR19/FR24 exception scoped to action groups)
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 5d22a75

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

Component Size

Component Import before Import after Delta CSS before CSS after Delta
core/Banner 48.0KB 48.0KB 0B 9.9KB 9.9KB 0B
core/ButtonGroup 8.4KB 8.4KB 0B 3.3KB 3.3KB 0B
core/ToggleButton 44.3KB 44.3KB 0B 9.3KB 9.3KB 0B
core/ToggleButtonGroup 6.3KB 6.3KB 0B 3.2KB 3.2KB 0B
core/Toolbar 39.9KB 39.9KB 0B 7.7KB 7.7KB 0B

Sizes are gzip. Import counts the JavaScript the import loads eagerly; CSS counts the package stylesheet rules referenced by the modules it can load from its own package, lazily loaded ones included.

Bundle Size Summary

Gzip figures measure each package's entry file, not the code an import pulls in.

Package Entry (ESM) Entry gzip before Entry gzip after Delta
@astryxdesign/core 4.9KB 1.2KB 1.2KB 0B
Package stylesheet Gzip before Gzip after Delta
@astryxdesign/core 34.9KB 34.9KB 0B

Accessibility Audit

Status: No accessibility violations detected.

Visual Regression

57 of 504 shot(s) changed.

A change here is a question, not a failure: check whether the after is the
picture you intended. Record that review on the PR. Baseline maintenance is an
explicit dispatch of CI; this report never rewrites the baseline or adds a release gate.

component story theme mode pixels
Selector Indicator Space Bottom Sheet Narrow End RTL neutral light 753,933
Selector Indicator Space Bottom Sheet Wide Start neutral light 740,072
RichTextEditor Markdown parity: side by side probe dark 558,840
RichTextEditor Markdown parity: side by side probe light 540,756
Chat Full AI Chat probe light 230,823
Chat Full AI Chat probe dark 229,805
AppShell With Mobile Nav probe light 217,010
AppShell With Mobile Nav probe dark 216,960
Collapsible Dividers probe dark 66,445
Collapsible Dividers probe light 66,445
AppShell Playground neutral dark 65,198
AppShell Playground neutral light 63,788
Chat Default neutral dark 36,304
Chat Default neutral light 35,767
Chat Empty State probe light 23,531
Chat Empty State probe dark 22,622
ChatAutoScroll Scroll Behavior Comparison neutral dark 15,378
ChatAutoScroll Scroll Behavior Comparison neutral light 14,107
Chat With Attachments probe light 13,406
Chat With Attachments probe dark 11,232

and 37 more.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

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

Thanks for pulling press, slide and act-on-lift into one family contract; the direction is owner-approved, but a few current records still contradict it, so changes are requested before this lands. Each fix is an edit to the records in this pull request:

  • spec:AST-094 FR11 and its verification row still say a control activates only through the browser's click, which never reaches the lifted button after a slide.
  • FR5's fine-pointer budget leaves menus no room for the post-release click swallower that module:DropdownMenu/useMenuPress PR2 and FR5 rely on.
  • component:ButtonGroup, component:Banner and family:buttons FR11 keep ownership statements the family overrides, and AST-094's Current-state impact does not name the change.
  • members needs component:DateTimeInput, the date wheels need an answer for their drag-to-scroll, and DEC-17 should drop "a segmented control".

[Automated review]

Comment on lines +321 to +323
FR26–FR28 are the shared rule of `family:action-groups`: every member, not
only menus, follows them under the family's scope, governed by the one
internal `actionGroupTracker` (DEC-17).

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 makes FR26 bind every member, and FR26 ends with "the browser's click for that gesture acts on nothing". FR11 (lines 184–208) still says a control "activates by the browser's click on a lift inside … its activation stays the browser's click", and the FR11/FR12 verification row fails "a control activated other than by the browser's click". After a slide no browser click reaches the lifted button, so ButtonGroup, Toolbar, AlertDialog, Banner and Toast buttons cannot meet family FR1 without breaking FR11. Suggest amending FR11 and that row so a control inside a family:action-groups member activates at the lift through the group's tracker, the way FR11 already gives menu rows the cell profile.

**Decider:** `cixzhang`, `2026-10-26`

A person who presses one action and slides to another expects the same result
in a menu, a segmented control, an alert's buttons, or a button group. One

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 makes SegmentedControl a named exception that "this family's rule does not bind", but this sentence lists a segmented control among the groups that behave the same, and the next sentence says one tracker governs every group. Suggest "a menu, an alert's buttons, a button group, or a toolbar", and scoping the next sentence to the family's members.

Comment thread docs/families/action-groups.md Outdated
Comment on lines +122 to +126
- **FR5 — Every pointer, one target rule.** The behavior applies to every web
pointer, mouse and pen as well as a finger. Under a fine pointer a group
keeps one passive `pointerdown` listener on its element and adds document
listeners only during a press, removing them at the release; nothing else
is idle (`spec:AST-094` FR19, FR24). The 44 px target is each member's hit

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Menus are members, and useMenuPress keeps a window capture click listener installed once per document (its PR2) to swallow the browser's click, which arrives after the release (its FR5); the shipped hook also cancels a finger's pointerdown (packages/core/src/hooks/useMenuPress.ts line 813). "Removing them at the release; nothing else is idle" and a passive pointerdown leave no room for either. Suggest allowing one per-document click swallower armed from the release until the gesture's click, and a cancelable pointerdown where a member needs it, or amending useMenuPress PR2 and FR5 in this pull request.

component:ContextMenu,
component:Selector,
component:MultiSelector,
component:DateInput,

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 membership rule names the date wheels of DateInput and DateTimeInput (lines 66–67), but only component:DateInput is listed. pnpm specs:for routes this family to DateInput and the shared Wheel.tsx, while DateTimeInput.tsx and TouchDateTimeField.tsx get only the five global FR routes; TimeInput's TouchTimeField.tsx mounts the same Wheel and is neither listed nor excluded. Separately, a wheel is a scroll container whose mouse drag scrolls it (usePointerDragScroll.ts), which FR1 and FR2 under FR5 would turn into tracking; consider an allowed variation (a drag scrolls, a tap acts) or moving the wheels to the exclusions.

Comment thread docs/families/action-groups.md Outdated
Comment on lines +150 to +153
| Menus, Selector, MultiSelector, date wheels | Track through `actionGroupTracker`; `useMenuPress` converges onto it. |
| AlertDialog's actions | Track the actions only. |
| ButtonGroup, ToggleButtonGroup, Toolbar | Track sibling buttons; a field inside is skipped; toggles follow FR4. |
| Banner and Toast action rows | Track only a row with two or more sibling actions. |

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 adoptions contradict current records this pull request leaves as they are: component:ButtonGroup does not own "Each member's own surface, naming, activation, pending, and disabled rendering", component:Banner does not own "Button geometry, interaction layers, tooltip painting, or consumer-supplied endContent", and family:buttons FR11 says "The group owns only the behavior and surface declared by its group mode." AST-094's Current-state impact names neither this family nor its members. Suggest amending those statements, saying where focus goes after a slide, and adding the Current-state impact entries.

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 1e17099

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

The action-groups contract and its amendments are owner-approved for this head, and the earlier Toolbar, InputGroup and navigation-menu findings are fixed, but one membership gap needs an edit before this lands. The family lists SideNavHeading's product menu as a member, while TopNavHeading renders the same popover with its own role="menu" around the caller's menu (TopNavHeading.spec.md FR7) and appears nowhere in members, the membership rule, the adoption row, the gap sentence or INV13's gap note; please add it beside SideNavHeading, or exclude it with a reason. Two smaller points: TopNavMegaMenu's panel also meets the membership rule and is on neither list, and ToggleButtonGroup's verification row still names "the absence of group listeners" as PR1's test gap.

[Automated review]

Comment thread docs/families/action-groups.md Outdated
today:

- menus: DropdownMenu, ContextMenu, and their sub-menus, the navigation
menus of TopNav (TopNavMenu), SideNavHeading, and NavHeadingMenu; and

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

TopNavHeading is missing here. Its current record gives it the same product menu as SideNavHeading's: FR7 "a role="menu" region named by heading … that holds menu" (packages/core/src/TopNav/TopNavHeading.spec.md line 125), and TopNavHeading.tsx line 554 renders it exactly as SideNavHeading.tsx line 536 does. As written, pnpm specs:for packages/core/src/TopNav/TopNavHeading.tsx does not reach this family, and the same NavHeadingMenu would track a held pointer in a SideNav header but not in a TopNav header. Suggest adding component:TopNavHeading to members and naming TopNavHeading's menu here, in the adoption row (line 171), the gap sentence (lines 177–179) and INV13's gap note, or excluding it with a reason.

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 2ebd152

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

The action-groups contract and its amendments are owner-approved for this head, and the TopNavHeading, TopNavMegaMenu and ToggleButtonGroup verification-row fixes hold, but the membership still needs one edit before this lands. Three current components match the membership rule and appear on neither list: TabMenu's role="menu" of menuitemradio options (TabMenu.spec.md FR4), DateRangeInput's labeled group of preset buttons (DateRangeInput.spec.md FR2), and SideNavItem's collapsed-rail flyout (SideNavItem.spec.md FR9). Please name each as a member, with its adoption row and INV13's gap note where it applies, or exclude it with a reason, as you did for TopNavHeading and TopNavMegaMenu. The description also still calls FR5's listener passive and says useMenuPress converges onto the tracker, which the records no longer say.

[Automated review]

Comment on lines +68 to +70
A component is a member when it renders a set of two or more sibling
momentary, toggle, or option actions that a person chooses one of. Members
today:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Three current components meet this rule and appear neither in the list below nor under "These are not members": TabMenu's role="menu" of menuitemradio options (packages/core/src/TabList/TabMenu.spec.md FR4, TabMenu.tsx lines 405 and 425), DateRangeInput's presets, "an independent button in one labeled group" (DateRangeInput.spec.md FR2), and SideNavItem's collapsed-rail flyout (SideNavItem.spec.md FR9). pnpm specs:for packages/core/src/TabList/TabMenu.tsx does not reach this family. Suggest naming each as a member (with members, its adoption row and INV13's gap note where it applies) or excluding it with a reason, as was done for TopNavHeading and TopNavMegaMenu.

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec fe7bf01

…oups

# Conflicts:
#	docs/architecture/interaction-modality.md
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 514dd56

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026

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

The action-groups contract and its amendments are owner-approved for this head, and the TabMenu, DateRangeInput and SideNavItem membership gap is closed, but one record line still needs an edit before this lands. spec:AST-094's Current-state impact names the family's members by hand (docs/specs/AST-094-touch-presentations/spec.md lines 605–609) and leaves out DateRangeInput's presets, which this head makes a member; please add them, or point that line at the family's membership so the two cannot drift. The description's member and exclusion lists also predate the latest membership edit.

[Automated review]

Comment on lines +605 to +609
- `family:action-groups` members (menus, Selector's and MultiSelector's
options, the date and time wheels, AlertDialog's actions, ButtonGroup,
ToggleButtonGroup, Toolbar's buttons, and Banner's and Toast's action rows):
a press that slides across sibling actions acts on the action it lifts on
(FR26–FR28), under every pointer.

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 list names the family's members by hand and leaves out DateRangeInput's presets, which docs/families/action-groups.md now makes a member (line 83, adoption row at line 187). Add "DateRangeInput's presets", or point this line at the family's membership so the two cannot drift.

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 63b7b09

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

Full review of 63b7b09; the incremental delta could not be read: the merge of main rewrote two spec records in its conflict resolution.

The action-groups contract and its amendments are owner-approved for this head, and the earlier gap is closed: spec:AST-094's Current-state impact now points at the family's membership rule, so DateRangeInput's presets are covered and nothing blocks. Two optional cleanups remain: run Prettier on docs/architecture/interaction-modality.md, whose INV13 verification row lost its padding in the merge, and consider amending component:AlertDialog's draft PR1 ("only listener is the small-screen media query") before its actions adopt the family.

[Automated review]

| INV10 | Source boundary check over Core and Lab, a Lab-to-Core comparison on one recorded stream, and release-filter unit tests over recorded iOS pan and scroll-view streams | A Core component with its own sample buffer, velocity math, or release filter; a Lab component with its own sample buffer, velocity math, or release filter; a Lab component reporting a different velocity than Core for the same stream; an export of the tracker; a non-zero release after a still pause |
| INV11 | Evidence gap: not implemented yet; the shared hover recognizer and its adopters' move onto it are pending. Then: unit tests of the hover recognizer's intent and browser tests of its adopters with a mouse, a pen, a finger, and the keyboard | A show without the owner's entry delay, a missed handoff within a group, a delayed keyboard show, a hover-shown surface that closes before the pointer reaches it, a finger served by it, or a second hover-timing implementation fails |
| INV12 | Unit tests of native scroll's progress and real-browser proofs of its adopters | Progress routed through React state, a missing edge, travel under Reduce Motion, use where native scroll-driven animation is supported, or a public export fails |
| INV13 | Source boundary check over Core and each family member's pointer-stream tests | A member outside menus and Selector's and MultiSelector's options that tracks a held pointer across its actions with its own code, or a public export of the tracker, fails |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit: this row lost its padding in the merge from main, so prettier --check now flags the file (it passes on main), and the lint-staged prettier --write hook will re-pad the whole row as churn in the next commit that touches it. Running Prettier on the file fixes it.

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 63b7b09

@cixzhang
cixzhang merged commit b393144 into main Oct 28, 2026
76 checks passed
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 28, 2026
… base

interaction-modality.md and AST-094 take main's text: the branch's
versions were meta#8622's own, which main now carries as merged.
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
@github-actions
github-actions Bot deleted the spec/ast094-action-groups branch October 31, 2026 06:09

This branch was successfully deployed

1 active deployment
Preview — 63b7b093 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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant