Skip to content

spec(ContextMenu): the context menu's three springs as motion roles, and the lift's four elements - #8830

Draft
vjeux wants to merge 2020 commits into
meta:mainfrom
vjeux:uikit/contextmenu-records
Draft

vjeux wants to merge 2020 commits into
meta:mainfrom
vjeux:uikit/contextmenu-records

Conversation

@vjeux

@vjeux vjeux commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #8693, whose code these records describe; this PR's own change: vjeux/astryx@b9608e9...5bd8258

Exact decision needed

Part of the UIKit-parity program. #8693 lands ContextMenu's touch presentation (the lift, the menu's grow and its shrink back) without record changes, as asked there. Two of those changes are ContextMenu's own and are here, for approval at this exact head: FR4's three springs and DEC-2. The other two stay where they started: the exit copy (architecture:layer-runtime INV13) and the spec:AST-057 reword are #8186's, and the lift module's line in the layer runtime is #8485's.

Existing authority searched

spec:AST-094 FR4 and FR33, component:ContextMenu FR10, architecture:layer-runtime INV7 and INV15, and the open-layer allowance (spec:AST-017 FR60, DEC-29). None names the context menu's springs or the lift's elements; the open #8186 and #8485 own the adjacent layer-runtime text.

Decision

  • spec:AST-094 FR4: springs.contextMenuLift (damping ratio 0.8, response 0.32 s), springs.contextMenuDismiss (0.85, 0.35 s) and springs.contextMenuMorph (0.8, 0.3 s), measured on UIContextMenuInteraction in the iOS 26.2 simulator, join the motion roles, owned by component:ContextMenu (DEC-5), whose record lists them. The registry's test compares its roles with FR4's rows, so the three lines that register them, and the two tests that name them, are the only code here.
  • component:ContextMenu DEC-2, a consistency decision from spec:AST-017 FR60 and DEC-29, architecture:layer-runtime INV15 and spec:AST-094 FR33: while a lifted menu is open, the page has four more elements than under a fine pointer (the lift layer, the shadow, the box the menu is placed against, the placeholder), each with one job; none is themeable or mounted at rest, and no content is mounted twice.

Rejected alternatives

DEC-2 keeps one line: a copy of the trigger above the tint, which mounts the trigger's content twice.

Claim scope and non-goals

These two claims only; no public API, prop or theme token changes.

Evidence and implementation relationship

The behavior is #8693's: the springs' measurements (iOS 26.2 simulator) for FR4; the element counts in ContextMenuLift.interaction (Chromium and WebKit) and ContextMenu.test.tsx's lift suite for DEC-2.

Writing rubric results

  • R1 narration: none; both claims are in the present tense.
  • R2 research in the record: DEC-2's element counts are INV7's required evidence and stay; no surveys or option comparisons.
  • R3 evidence naming a change: none; sources name measurements and tests.
  • R4 rejected alternatives over one line or outside a decision: one, inside DEC-2, one sentence.
  • R5 pull request or issue references remaining: none.
  • R6 ruled open questions still listing options: none.

Scope

  • This PR contains specification records only. (Plus the three registry lines FR4's test requires, and the two tests that name those roles.)
  • 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.
  • 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; any ownership collision is resolved or called out for human decision.
  • 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 passes; Prettier and git diff --check are clean.
  • vitest run packages/core/src/motion and ContextMenuLift.test.tsx: 719 pass, including the registry's FR4 check.

vjeux added 30 commits October 27, 2026 00:48
The morph spring moved into the springs table as the motion role
springs.contextMenuMorph: the menu's growth from the finger keeps its start
width in the context menu's table and plays on the role.
The lift's springs are motion roles now: the close keeps its springs, the
preview's return and the tint on springs.contextMenuDismiss and the menu's
shrink on springs.menuClose, and the role test names them.
The case walked ContextMenu, DropdownMenu, useLongPress and useMenuPress's
imports and failed on the UIKit gestures table: it kept that table out of
the menus' bundles, the lazy design the no-lazy-load ruling and direct
imports retire. The long-press numbers keep one source, still pinned by the
table, recognizer and hooks case and by the hooks reaching longPress.ts.
…8589's watcher test reads its end; the gestures-table gate retired) into meta#8360
…8589's watcher test reads its end; the gestures-table gate retired) into the every-pointer host
… watcher test's tail fix) into the input hold
…of the document; DEC-22's screen-edge rule) into the animator's presentation start
…xposes no ontouchstart is taken once

Chromium fixes ontouchstart when the page is created, so turning touch
emulation on later sends Touch Events without it: the host reads Pointer
Events for the finger and must not read its Touch Events too. A one-finger
long press fails here, the finger tracked twice.
…from their pointer events

install() reads a finger from its pointer events when the window has no
ontouchstart, but the four touch handlers still took the Touch Events a
browser sends anyway (Chromium after touch emulation turns on), tracking
the finger twice, so a one-finger long press never recognized. Each touch
handler now returns while usesTouchEvents is false, after noting the
fingers down, as install()'s comment promised.
…n before the click guard

internal/motion.ts (meta#8562) re-exports UIKIT_CLICK from the barrel, so a
tree carrying both fails typecheck and the Storybook build since this
pull request dropped it. The barrel keeps it, with the click guard's
window, and the table of every gesture group still leaves it out. Fails
until the barrel exports it.
…'s window

This pull request dropped UIKIT_CLICK when the click guard took the
stray-click window over, but importers written before it read the barrel's
UIKIT_CLICK (internal/motion.ts in meta#8562 re-exports it), so a tree carrying
both failed typecheck and the Storybook build. UIKIT_CLICK stays in the
barrel, deprecated, with the guard's 400 ms (CLICK_GUARD_WINDOW_MS), and
UIKIT_GESTURES still leaves it out.
…ter events) into the every-pointer host

# Conflicts:
#	packages/core/src/gestures/__tests__/GestureHost.test.ts
Bundled with the size report's esbuild options, none of Core's public
entries carries a byte of gestures/ until a component imports it
(architecture:react-component-runtime INV13). The click guard in utils/
imports the haptic switch's attribute from gestures/, so the root and hooks
entries carry gestures/hapticSwitch.ts: both cases fail until it moves.
…blic entries carry no gesture code

The click guard (utils/clickGuard.ts) imported HAPTIC_SWITCH_ATTRIBUTE from
gestures/hapticSwitch.ts, which put gesture code in Core's root and hooks
entries. The attribute moves to utils/hapticSwitch.ts, as the coarse-pointer
query did; the click guard, the gesture host and haptics read it there, and
gestures/hapticSwitch.ts re-exports it, deprecated, for importers written
before the move. The module-graph case follows the new path.
vjeux added 8 commits October 28, 2026 12:11
…peat control owns only its hold

- A pen held still requests through the hold, and the browser then fires
  the pen's own context menu event, during the hold or at its lift: a
  second request for one gesture. One request only, the hold's.
- The engineering owner's ruling on meta#8670: a hold-repeat control inside a
  row owns only its hold; its right click and Shift+F10 still reach the
  row's context request. Today both are refused there.
Both fail until the context request refuses the pen's echo and leaves a
kept press only its hold.
…eat control owns only its hold

- The context request refuses a pen's own menu event during its hold and
  within the menu echo after its lift, as it refuses a held finger's and a
  key's. (A pen's barrel button already starts no press, from meta#8665.)
- A hold-repeat control inside a row keeps only its hold: the row's right
  click and Shift+F10 reach the request through it, as the engineering
  owner ruled.
… with no options a record does not need

The engineering owner's ruling on meta#8670: cut the two-stage commitMs,
requestMs and requestOnLift, and the allowableMovement and isPerAxis
tolerance, until a record calls for them. The hold is UIKit's long press,
500 ms and 10 px as a distance, and requests as it begins; onHold reports
it committed, then ended or cancelled. LongPressGestureRecognizer loses
isPerAxis and geometry its axisTravel, with their tests.
…, with no stages (spec:AST-094 FR43)

Red: meta#8670 cuts the context request's two-stage hold (commitMs,
requestMs, requestOnLift) and its per-axis allowance, as asked there:
the hold is UIKit's long press and requests as it begins. ContextMenu
and the factory's menu still open a held finger at 735 ms, open at a
lift after 400 ms, and allow 11.5 pt on each axis. They should open at
500 ms under any primary pointer, open nothing for a lift before it, and
open nothing for a drift past 10 pt.
…press, with no options) into the headless context menu

packages/core/src/utils/pressFeedback.ts and its test: meta#8670's text,
with this stack's press-paint subscription (subscribePressPaint, which
ContextMenu's press feedback follows) kept beside it.
packages/core/src/hooks/useLongPress.ts, its test, its page and
useMenuPress.ts: meta#8670's text. This stack's useLongPress `stages` and
press start times served ContextMenu's two-stage hold, which no longer
exists (the context request opens ContextMenu, and its hold has no
stages), so they go, with their changeset and the hooks entry's
LongPressStage type.
… with none of the hold options meta#8670 cut (spec:AST-094 FR43)

meta#8670 cuts the context request's two-stage hold (commitMs, requestMs,
requestOnLift), allowableMovement and isPerAxis, as asked there: the hold
is UIKit's long press and requests as it begins. ContextMenu and the
factory passed them for UIContextMenuInteraction's 400 ms commit, 735 ms
menu, open at a lift and 11.5 pt on each axis. They pass none now: under
any primary pointer a held finger gets the menu at 500 ms, a lift before
it is a tap, and a drift past 10 pt (a distance) opens nothing. Under the
touch model the commit, now the long press, plays the haptic and the
press feedback plays UIKit's press curve until the menu shows.

The UIKit table's per-axis drift guess has no reader left and goes. The
tests, the interaction specs, the docs, the story and the changesets
that described the two-stage hold describe the long press; the tests of
a lift after the commit and of a drag between the commit and the menu
go with that window.
…ow, and Escape returns focus to it from another row's focus (spec:AST-094 FR33, spec:AST-107 FR8)

The story's Inbox row, with Drafts focused first: a held finger opens
its menu with the lift up beside it, and Escape closes the menu and
focuses Inbox, in Chromium and WebKit.
…and the lift's four elements

spec:AST-094 FR4: springs.contextMenuLift, springs.contextMenuDismiss and
springs.contextMenuMorph, measured on UIContextMenuInteraction, join the
motion roles, and the registry registers them (its test reads FR4's
rows). component:ContextMenu DEC-2: while a lifted menu is open the page
has four more elements, each with one job, and none at rest, with their
element counts. The code these records describe is in meta#8693.
@vercel

vercel Bot commented Oct 28, 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:41pm UTC

Request Review

@cixzhang

Copy link
Copy Markdown
Contributor

Posted by Cindy's agent on her behalf.

Thanks for splitting this out. Both changes are decided by current records, so this can be approved with two wording edits:

  • The three springs.contextMenu* rows in spec:AST-094 FR4 fit FR4's measured-role model (DEC-5). Please cite ContextMenu's record as their owner.
  • ContextMenu DEC-2 (four lift elements while open, none at rest, no copy) applies the open-layer allowance (AST-017 FR60 / DEC-29), layer-runtime INV15 and AST-094 FR33. Please record it as a consistency decision citing those, rather than "pending owner review".

The layer-runtime INV13 line and the AST-057 reword aren't in this PR. If #8693 still carries them, they need their own spec PR.

…heir owner, and DEC-2 is a consistency decision

As asked on meta#8830: spec:AST-094 FR4 says component:ContextMenu owns the
three springs.contextMenu* rows (DEC-5), and ContextMenu's record lists
them. DEC-2 records its decider as consistency with current records:
spec:AST-017 FR60 and DEC-29, architecture:layer-runtime INV15 and
spec:AST-094 FR33.
@vjeux

vjeux commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, both edits are in (vjeux@5bd825809f): FR4 names component:ContextMenu as the owner of the three springs.contextMenu* rows (DEC-5), and ContextMenu's record lists them; DEC-2's decider is consistency with spec:AST-017 FR60 and DEC-29, architecture:layer-runtime INV15 and spec:AST-094 FR33.
#8693 no longer carries the INV13 line or the AST-057 reword: its records are back to main's text since vjeux@64c84a369c. Both are #8186's own open record changes.

@cixzhang

Copy link
Copy Markdown
Contributor

/approve-spec 5bd8258

@astracat-bot

astracat-bot Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟡 Reviewed (for maintainers only) Oct 29, 2026, 12:06 AM UTC

@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 two spec claims here (the three springs.contextMenu* rows in spec:AST-094 FR4 and ContextMenu DEC-2) carry an engineering owner's approval for this head, but the branch also carries all of #8693, so the diff is 260 files and about 46,000 changed lines instead of the six files the two spec commits touch. Please land #8693 first and rebase this branch onto main, so this pull request contains only its own change. That rebase also removes the new FR41 sentence on claimed mouse and pen drags, which the description does not list; if it stays, it needs the reason and the fine-pointer before/after evidence spec:AST-017 FR59 asks for.

[Automated review]

Comment on lines +477 to +479
(`spec:AST-017` FR59; DEC-9). A mouse or pen drag that a recognizer claims
suppresses text selection for that drag only; an unclaimed mouse drag keeps
today's 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 sentence changes what a claimed mouse or pen drag does, but the description's scope lists only FR4's rows and DEC-2. It comes from #8693's branch, so it leaves this pull request once #8693 lands and the branch is rebased; if it stays, spec:AST-017 FR59 asks for the reason and fine-pointer evidence before and after.

@vjeux

vjeux commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

This PR is stacked on #8693 on purpose: the two records describe code only #8693 has, so it can't sit on main alone yet. Its own change is these six files: vjeux/astryx@b9608e9...5bd8258.
Once #8693 merges, this branch takes main and holds only them.
The FR41 sentence isn't this PR's: it is #8665's FR41 consistency edit, asked for there, and it reaches this diff through #8670 and #8693 and leaves with them.

@vjeux
vjeux marked this pull request as draft October 29, 2026 22:40
@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Oct 31, 2026

This branch was successfully deployed

1 active deployment
Preview — 5bd82580 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. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants