Skip to content

docs(spec): ContextMenu records its shipped contract as current - #9125

Merged
cixzhang merged 6 commits into
mainfrom
spec-backfill/ContextMenu
Oct 31, 2026
Merged

cixzhang merged 6 commits into
mainfrom
spec-backfill/ContextMenu

Conversation

@cixzhang

@cixzhang cixzhang commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Exact decision needed

Make component:ContextMenu current and add component:ContextMenuItem (it has its own .doc.mjs), recording shipped behavior. No behavior, API, default, or DOM change.

Existing authority searched

Decision

  • ContextMenu.spec.md becomes 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 at component:DropdownMenu FR11 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 that ContextMenuItem is DropdownMenuItem under 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, and themingTargets.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, against spec:AST-107 FR8.

  1. Under a finger the menu keeps the layer's own entrance and exit, a scroll that starts outside leaves it open, the opening finger cannot slide to a row, a block trigger is not lifted, and a pen press outside closes it at the press, against spec:AST-094 FR31, FR32, FR33.

Writing rubric results

  • R1 narration: none; present tense; gap notes say what happens now.
  • R2 research in the record: none.
  • R3 evidence naming a change: none.
  • R4 rejected alternatives over one line or outside a decision: none beyond DEC-1 as written.
  • R5 pull request or issue references remaining: none.
  • R6 ruled open questions still listing options: none.

Scope

  • This PR contains specification records only.
  • It records observable behavior and preserves equivalent internal implementations.
  • Any named internal mechanism is intentionally public, names its dependent caller/system, and explains why an equivalent implementation would not satisfy the contract: none named.
  • Verification states the evidence layer and failure signal without prescribing CI job/workflow topology.
  • It records one intentional durable decision, not a rescue for a separable tagalong.
  • The canonical knowledge owner, affected current claims, and overlapping open work are named; no ownership collision.
  • Adjacent API, visual, accessibility, composition, and implementation facts remain outside scope unless this exact decision depends on them.
  • Public text and artifacts contain no internal Meta context.

Validation

  • pnpm check:knowledge: aligned
  • Prettier: clean
  • git diff --check: clean

@astracat-bot

astracat-bot Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

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

@vercel

vercel Bot commented Oct 31, 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 31, 2026 10:53am UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 31, 2026
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Oct 31, 2026
@cixzhang
cixzhang marked this pull request as ready for review October 31, 2026 10: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.

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 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

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: inside a code span the backslashes render literally, so this reads presentation=\"popover\". Suggest presentation="popover".

…tside gaps, and qualifies the disabled-row skip

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

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec a74bfda

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Oct 31, 2026
@cixzhang
cixzhang merged commit 6b63e75 into main Oct 31, 2026
51 checks passed

This branch was successfully deployed

1 active deployment
Preview — a74bfda1 Deployed Oct 31, 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