Repository navigation
docs(spec): ContextMenu records its shipped contract as current - #9125
Conversation
|
AI review status for this pull request.
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
This spec backfill is on hold for an owner decision. It makes component:ContextMenu current and adds component:ContextMenuItem, and both write requirements that no current record states yet, so an engineering owner has to approve them by commenting /approve-spec <full head SHA> on this pull request. The author can do this as an engineering owner. Independently, three statements in the ContextMenu record need fixing before it lands:
- FR5 says the long press that opened the menu is its opening press, but ContextMenu never hands that finger to the press model; please write it as an implementation gap against AST-094 FR33 and list it under Current-state impact.
- FR5 says a pen press outside closes the menu at the press, but AST-094 FR33 makes a pen's press outside follow FR32; please record the pen case as a gap.
- FR8 makes DropdownMenu FR17's disabled-row skip a requirement, but FR17 says it is not one until DropdownMenu OQ1 is ruled; please carry that qualification or drop the clause.
[Automated review]
| | FR2 | The `context-menu` target stays on the painted Pointer menu surface and alternative Touch menu surface, not on the Trigger area or cursor anchor. | `architecture:component-theming-surface`; current source | | ||
| | FR3 | Pointer action rows retain DropdownMenu ownership. The touch frame retains BottomSheet ownership, while data-driven touch lists and rows retain List ownership. | Current source and owner docs | | ||
| | FR4 | Compound `menuContent` remains a caller-supplied pointer-menu interior and may also render inside the touch frame; the data-driven touch path instead converts the same item data to List and ListItem presentation. | Current source and tests | | ||
| | FR5 | The Pointer menu surface carries `data-astryx-menu-press` and follows `module:DropdownMenu/useMenuPress` FR1–FR7 and FR11: the row under the release acts, the highlight follows a held pointer, a mouse released outside closes and a finger leaves the menu open, the stray click never acts, `touch-action` follows overflow, and the long press that opened the menu is the opening press. The long press that opened the menu does not close it as it lifts; a tap outside closes it, and a mouse or pen press outside closes it at the press. Under a finger the menu MUST grow out of where it was opened and shrink back (`spec:AST-094` FR31), MUST close as a pan that starts outside it begins (FR32), and, opened by a held finger on a block trigger, MUST lift the trigger above the page with the menu beside it (FR33). Implementation gap: it keeps the layer's own entrance and exit, a scroll that starts outside leaves it open, and no trigger lift exists. Right-click, Shift+F10, long-press invocation, and keyboard navigation are as FR1 and FR8 state. | `module:DropdownMenu/useMenuPress`; `spec:AST-094` FR26–FR33 | |
There was a problem hiding this comment.
Two statements here read as shipped but do not match CURRENT. First, "the long press that opened the menu is the opening press": ContextMenu never hands that finger to useMenuPress (no trigger props, no onTriggerPress), so a held finger cannot slide to a row and pick it, which is the AST-094 FR33 handoff. Second, "a mouse or pen press outside closes it at the press": AST-094 FR33 makes a pen's press outside follow FR32. Please write both as implementation gaps and add them to Current-state impact.
| | FR5 | The Pointer menu surface carries `data-astryx-menu-press` and follows `module:DropdownMenu/useMenuPress` FR1–FR7 and FR11: the row under the release acts, the highlight follows a held pointer, a mouse released outside closes and a finger leaves the menu open, the stray click never acts, `touch-action` follows overflow, and the long press that opened the menu is the opening press. The long press that opened the menu does not close it as it lifts; a tap outside closes it, and a mouse or pen press outside closes it at the press. Under a finger the menu MUST grow out of where it was opened and shrink back (`spec:AST-094` FR31), MUST close as a pan that starts outside it begins (FR32), and, opened by a held finger on a block trigger, MUST lift the trigger above the page with the menu beside it (FR33). Implementation gap: it keeps the layer's own entrance and exit, a scroll that starts outside leaves it open, and no trigger lift exists. Right-click, Shift+F10, long-press invocation, and keyboard navigation are as FR1 and FR8 state. | `module:DropdownMenu/useMenuPress`; `spec:AST-094` FR26–FR33 | | ||
| | FR6 | With `triggerAs="span"` the Trigger area is an inline element that owns a box, so the cursor anchor measures from the trigger's own rect on every invocation path. Right-click, Shift+F10, long-press, and `context-menu` target placement all use that box. | DEC-1 | | ||
| | FR7 | Sub-menu rows follow `component:DropdownMenu` FR11 inside the pointer menu: ContextMenu mounts the same drill-in view stack, named after its `label`, so a sub-menu drills in on a phone here too. A sub-menu with a header or footer shows its rows alone here (`component:DropdownMenu` FR20). | `component:DropdownMenu/DEC-4` | | ||
| | FR8 | In the pointer menu ArrowDown on the last enabled row wraps to the first and ArrowUp on the first to the last; PageDown and PageUp page as in `component:DropdownMenu` FR12; a held key's auto-repeat does not activate; typeahead matches the row's label element alone, and arrow keys and typeahead skip disabled rows (`component:DropdownMenu` FR17). | Current source and tests | |
There was a problem hiding this comment.
component:DropdownMenu FR17 says the disabled-row skip is "Shipped behavior, not a requirement until OQ1 is ruled". As written, FR8 makes it a ContextMenu requirement ahead of that ruling. Please carry the qualification or drop the clause.
| | Behavior | Right-click, Shift+F10 or the Menu key, and a touch long press open a menu at the cursor (or the trigger's bottom-left from the keyboard), or a bottom sheet; rows follow DropdownMenu's rows and press model. | | ||
| | End-user impact | People reach a region's actions by mouse, keyboard, or a held finger; a sub-menu drills in on a phone. Under a finger the menu does not grow from where it was opened or lift its trigger, and a scroll that starts outside leaves it open. | | ||
| | Builder impact | Builders wrap any region, including an inline reference in prose (`triggerAs="span"`). | | ||
| | Compatibility/readiness | Records shipped behavior; no API or DOM change; the released default under a finger moves as `spec:AST-094` FR25 lists, with `presentation=\"popover\"` keeping it. The touch grow, pan dismissal, and trigger lift (FR5) are listed under Current-state impact. | |
There was a problem hiding this comment.
Nit: inside a code span the backslashes render literally, so this reads presentation=\"popover\". Suggest presentation="popover".
…tside gaps, and qualifies the disabled-row skip
There was a problem hiding this comment.
This spec backfill waits on one owner decision: it makes component:ContextMenu current and adds component:ContextMenuItem, and both state requirements that no current record states yet. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author can do this as an engineering owner. The records' shipped claims match the source and tests, and each touch and headless-factory behavior that does not ship yet carries an implementation gap, so nothing else needs to change.
[Automated review]
| | Behavior | Right-click, Shift+F10 or the Menu key, and a touch long press open a menu at the cursor (or the trigger's bottom-left from the keyboard), or a bottom sheet; rows follow DropdownMenu's rows and press model. | | ||
| | End-user impact | People reach a region's actions by mouse, keyboard, or a held finger; a sub-menu drills in on a phone. Under a finger the menu does not grow from where it was opened or lift its trigger, and a scroll that starts outside leaves it open. | | ||
| | Builder impact | Builders wrap any region, including an inline reference in prose (`triggerAs="span"`). | | ||
| | Compatibility/readiness | Records shipped behavior; no API or DOM change; the released default under a finger moves as `spec:AST-094` FR25 lists, with `presentation="popover"` keeping it. The touch grow, pan dismissal, and trigger lift (FR5) are listed under Current-state impact. | |
There was a problem hiding this comment.
Optional: this row and End-user impact (line 42) list the grow, lift and pan gaps but not the opening-finger handoff or the pen press outside that FR5 and Current-state impact now record. Adding them keeps the projection in step with the body.
| - Pointer action-row presentation — delegated to `component:DropdownMenu`. | ||
| - Pointer action-row presentation — delegated to `component:DropdownMenu`; the | ||
| `ContextMenuItem`, `ContextMenuDivider`, and `ContextMenuGroup` exports are | ||
| DropdownMenu's components under these names (`component:ContextMenuItem`). |
There was a problem hiding this comment.
Optional: index.ts also re-exports ContextMenuCheckboxItem, ContextMenuRadioGroup, ContextMenuRadioItem and ContextMenuSubMenu from DropdownMenu; naming all seven (or "every ContextMenu* row export") keeps this list complete.
|
/approve-spec a74bfda |
Exact decision needed
Make
component:ContextMenucurrent and addcomponent:ContextMenuItem(it has its own.doc.mjs), recording shipped behavior. No behavior, API, default, or DOM change.Existing authority searched
pnpm specs:for packages/core/src/ContextMenu/ContextMenu.tsx→ current records that govern ContextMenu:spec:AST-107FR8,spec:AST-094,module:DropdownMenu/useMenuPress,family:overlay-dismissal,architecture:component-theming-surface,architecture:interaction-modality,architecture:layer-runtime,architecture:public-component-api, andcomponent:DropdownMenuwith its row records.ContextMenu.spec.mdexisted as adraft.ContextMenu.spec.md: spec(ContextMenu): the context menu's three springs as motion roles, and the lift's four elements #8830 rebases on top of this record (spring motion roles); spec(DropdownMenu): a disabled menu row is reached by the keyboard and stays inert (DEC-14) #9008 and fix(DropdownMenu, ContextMenu): Tab from any sub-menu level closes the whole menu #8870 rebase on top of this record (disabled rows; Tab from a sub-menu level).Decision
ContextMenu.spec.mdbecomes current (approved_by: cixzhang,approved_at: 2026-10-31), template 7, written from main today with existing content and attribution kept; FR7 and FR8 now point atcomponent:DropdownMenuFR11 and FR12, the draft's "pending implementation" touch rules become FR5's "MUST … Implementation gap" (grow and pan dismissal), and the shipped long-press, tap, mouse, and pen outside dismissal is stated. FR10 (built on the headless factory, an owner decision) is written as "MUST … Implementation gap" because the factory does not ship on main.ContextMenuItem.spec.md(new) records thatContextMenuItemisDropdownMenuItemunder the ContextMenu name.Rejected alternatives
None.
Claim scope and non-goals
Shipped behavior only, traced from
ContextMenu.tsx,index.ts, the docs, and the ContextMenu tests. No code is fixed.Evidence and implementation relationship
ContextMenu.test.tsx,ContextMenu.triggerProps.test.tsx,ContextMenuLongPress.a11y.chromium.spec.ts,ContextMenuCursorAnchor.a11y.browser.spec.ts, andthemingTargets.test.ts; each verification row names its cases and lists its evidence gaps.Current-state impact:
2. No headless context menu (
createContextMenu) ships, so ContextMenu owns its invocation, open state, and outside-press listener itself, againstspec:AST-107FR8.spec:AST-094FR31, FR32, FR33.Writing rubric results
Scope
Validation
pnpm check:knowledge: alignedgit diff --check: clean