Skip to content

feat(ContextMenu): a held finger grows the trigger on UIKit's lift spring - #9816

Draft
vjeux wants to merge 70 commits into
meta:mainfrom
vjeux:uikit/c20-contextmenu-growth
Draft

vjeux wants to merge 70 commits into
meta:mainfrom
vjeux:uikit/c20-contextmenu-growth

Conversation

@vjeux

@vjeux vjeux commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Stack: #8125 → #9816 → #8133 → #8201
Merge group: #8125, #9816, #8133

Behavior module: ContextMenu's press feedback.

User need

A held finger should see the trigger grow from 150 ms, as UIKit's view does, before the menu at 735 ms.

Problem and solution fit

  • The growth starts at the first frame at or after the touch's timestamp plus 150 ms, from rest, toward 1.0735× on springs.contextMenuLift.
  • It returns on springs.contextMenuDismiss when the hold ends with no menu or the menu closes, and holds while the menu is up.
  • None inline, behind a sheet, or under Reduce Motion. A nested trigger grows alone. An enabled trigger joins core's press machine.
  • The growth scales an inner box holding the trigger's children; the menu's anchor stays on the untransformed trigger (architecture:layer-runtime INV22).

Current specification

component:ContextMenu FR5 (since #9915) makes the long press a gesture component: the lift plays springs.contextMenuLift, the return springs.contextMenuDismiss (spec:AST-094 FR7, FR45). FR12 adds the growth on those springs. design:reduced-motion governs.

Public delta and caller burden

No API change. A block trigger grows about 7%; its docs ask for room. Its children sit in one inner box, laid out as before. ContextMenu carries core's motion engine eagerly, like 10 components on main. Eager JS gzip (example-vite), over #8125: +14.7 KB with one ContextMenu; +1.2 KB after a Switch.

Design and alternatives

  • A late frame starts a whole grow-in from rest, never a jump into the spring.
  • The springs play on the compositor, so a busy main thread does not stall them.

Evidence

spec:AST-094 Platform support:

  • Chromium (phone emulation) and WebKit (CI profile): the long-press spec's proofs, red first (Testing).
  • iOS 26.2 Simulator at efe3f12 (same motion): a 400 ms block freezes the frame clock 23 frames while the card keeps growing (strip); hold and close; early lift. Reduce Motion: no growth.
  • Real-device iOS Safari: the spec owners' check, not public.
  • Android (DEC-27): the Android takes at 4c936d1 predate the inner box; this head needs a retake.

Scope

  • One capability is added.
  • Unrelated fixes, cleanup, and policy changes were removed or split.
  • Consumer docs, focused tests, and migration evidence ship with the feature.
  • Public text and artifacts contain no internal Meta context.

Writing rubric results

Over component:ContextMenu: FR12 adds the growth on FR5's springs beside the hold's internal moments; Ownership names the trigger mark's module. Owner-gated.

  • R1 narration: none.
  • R3 evidence: FR12's basis names UIKit's measured moments and growth, and FR5's gesture-component decision.
  • R2 research, R4 rejected alternatives, R5 change references, R6 open questions: none.

Testing

This round, at 8fb305f, from review:

  • da14211 merges feat(ContextMenu): open from a held finger on iOS 26's timeline #8125's head 69e6dd8, whose comments stop naming a press feedback there. The touch layer's header keeps both sides' sentences.
  • cecb382, comments only: the timeline's header and the long-press spec name the press feedback again, since it ships here.
  • 3e17bd2, red first: in a same-origin iframe, a finger's growth starts at that window's onset. That is the first frame at or after 150 ms past the landing. At 850a9c2 it started in the first frame, dated on the page's clock.
  • 69a6851 fixes it. The press feedback reads the trigger's window for its frames and clock, and for another window its animator runs on that window's clock and frames. The page's own document keeps the defaults. A comment that only restated a name goes.
  • 1d6ec37, records only: the End-user impact row drops its "does not yet" sentence, a gap that Current-state impact states against FR5.
  • 8fb305f, comments only: the press feedback's file comment keeps one line; the rest repeated the hook's JSDoc.

Local checks at 1d6ec37, one worker (8fb305f changes one comment):

  • the tests this change reaches on main b8835b8 (test:ui --changed): 485 files, 14,214 passed;
  • the long-press spec, every case three times: Chromium 87 passed, 3 skipped; WebKit under the CI profile 66 passed, 24 skipped;
  • the keyboard-capped lists spec: Chromium 7 passed; WebKit 7 passed;
  • the gesture specs: Chromium 77 passed, 4 skipped; WebKit 69 passed, 12 skipped; presented hit testing 6 passed in each;
  • in Chromium: the ContextMenu audit (18 passed), long-press a11y (3 passed), cursor-anchor (3 passed) and menu hover-focus (7 passed) specs;
  • in WebKit: the cursor-anchor spec (3 passed);
  • the core typecheck, strict eslint and prettier on the changed files, changeset coverage, check:changesets and check:knowledge.

The previous round, at 850a9c2:

  • 4e93b5f replaces the module-level set of mounted triggers with a private attribute, data-astryx-context-menu-trigger, written and read through one owner module. A nested trigger still grows alone.
  • f908089, records only: component:ContextMenu's Ownership names that module, its writer and its reader.
  • 850a9c2 merges feat(ContextMenu): open from a held finger on iOS 26's timeline #8125's head fac565a. ContextMenuTouch.ts goes, so the touch layer imports contextMenuPress and contextMenuPressFeedback directly. A press that ends with no menu springs back and releases what the commit mounted. The long-press spec keeps both pull requests' cases.

Local checks at 850a9c2, one worker:

  • the tests this change reaches on main b8835b8 (test:ui --changed): 485 files, 14,213 passed;
  • the long-press spec, every case three times: Chromium 87 passed, 3 skipped; WebKit under the CI profile 66 passed, 24 skipped;
  • the keyboard-capped lists spec: Chromium 7 passed; WebKit 7 passed;
  • the gesture specs: Chromium 77 passed, 4 skipped; WebKit 69 passed, 12 skipped; presented hit testing 6 passed in each;
  • in Chromium: the ContextMenu audit (18 passed), long-press a11y (3 passed), cursor-anchor (3 passed) and menu hover-focus (7 passed) specs;
  • in WebKit: the cursor-anchor spec (3 passed);
  • the core typecheck, strict eslint and prettier on the changed files, changeset coverage, check:changesets and check:knowledge.

The previous head, 0b1f31c, merged #8125's head cae3644 with no conflict. There the press reads the page's time from the arena's injected clock, which is performance.now() in a browser. Before it, 2a36eba merged #8125's 45bfb44. There a menu timer that runs more than a frame late waits up to 50 ms for moves queued behind the block. 782cdbd merged #8125's cad79ec. There the press judges each coalesced sample at its own time. The menu waits at its moment only when the page is behind its input, so an on-time finger gets it at 735 ms. e25ec8c merged #8125's 88cc0d8, and its one conflict dropped the touch layer's pointer follow. c346da0 merged #8125's a4e0404, and three commits answer the review of 2ef1714:

  • d26b15b adds a set-height trigger with a full-height child to 'Touch / trigger layouts' and its layout case. It fails at that commit: the child measured 24 px in the 160 px trigger.
  • 7d23141 makes the inner box take the trigger's height in block layout, as it already does in flex and grid.
  • a75cf09 checks that the trigger keeps its own attributes, and that the inner box carries only its class.

Under a fine pointer, all 31 ContextMenu story captures match main's pixel for pixel: 18 stories at rest, 13 with the menu open. That ran at 55f84be, whose ContextMenu.tsx differs from this head's in one comment line. The change adds no public data-* attribute; 4e93b5f's private data-astryx-context-menu-trigger has its owner module (INV23).

Local checks at 0b1f31c, one worker:

  • the long-press spec: Chromium 28 passed, 1 skipped; WebKit under the CI profile 22 passed, 7 skipped;
  • the keyboard-capped lists spec: Chromium 7 passed; WebKit 7 passed;
  • the gesture specs: Chromium 76 passed, 4 skipped; WebKit 68 passed, 12 skipped; presented hit testing 6 passed in each;
  • the ContextMenu audit (18 passed), long-press a11y (3 passed) and cursor-anchor specs in Chromium (3 passed), and the cursor-anchor spec in WebKit (3 passed);
  • the ContextMenu, Layer, motion, DropdownMenu, hooks, gestures, utils and lift units (5,369 passed), the core typecheck, eslint, check:changesets and check:knowledge.

The previous head, 2ef1714, sat on a merge of #8125's head a5a0e12 (704cff3). The one conflict put two new browser cases side by side in the long-press spec. Three commits follow:

  • 06c942c adds the cases for architecture:layer-runtime INV21 and INV22 (spec:AST-059 DEC-9 and DEC-10, from #11024). A finger's or a pen's press leaves the open menu as an unpressed open. A layout case checks block, row and grid triggers against plain boxes.
  • 597e158 grows an inner visual box, and the touch changeset says so. The anchor stays outside it, and the script that cancelled the scale at the anchor is gone.
  • 2ef1714 turns the long-press cases' fifteen fixed waits into conditions. Each waits on the page clock from the touch, the menu's moment plus a task, the lift plus a frame, or a settled menu.

Red first, at fb27cdc (the same tree as 06c942c), in Chromium and WebKit: the press cases fail. The menu's box matched, but the trigger above the anchor carried scale 1.0735, and the anchor carried an inline counter-scale and translate. The layout case passed, as it should with no inner box.

Local checks at 9de7a99, which differs from this head only in that changeset sentence, one worker:

  • the long-press spec: Chromium 25 passed, 1 skipped; WebKit under the CI profile 19 passed, 7 skipped. Twice more each: Chromium 50 passed, 2 skipped; WebKit, with the keyboard-capped lists spec, 52 passed, 14 skipped;
  • the keyboard-capped lists spec in Chromium (7 passed);
  • the ContextMenu audit in Chromium (18 passed);
  • the cursor-anchor, long-press and menu hover-focus a11y specs in Chromium (13 passed), and the cursor-anchor spec in WebKit (3 passed);
  • the ContextMenu, Layer, motion, DropdownMenu and hooks units (3,317 passed), the core typecheck, eslint, check:changesets and check:knowledge.

Before and after, a finger held near the card's corner, at 450, 900 and 1,400 ms. The top row is before the inner box, the bottom row this head. The menu opens at the same place under the finger, and only the card grows: capture.

At earlier heads, below, "this head" means cc254d7.

Android exit timing (contextmenu-default): main none/70 ms, head none/90 ms (6 interleaved rounds; main max gap 107 ms)

Frames, not timers, decide the onset: Chromium holds a page's timers for about 100 ms after it handles a touchstart.

Replayed onto main cd748d0 after #9814's squash: 1cedda2 carries this pull request's earlier own diff (old head 63c7a6c; its message lists the folded commits), and the newer commits sit on top of it. It sits on #8125's 3d78dae, which now carries the menu's commit inside its deciding task and its commit-time mount; with those, this head's ContextMenu files equal 0c316d9's plus #8125's second-document test, its 735 ms proof, its PR1 sentences and one lint-directive comment line, so the menu behaves the same; the tree also carries main 92784f6, which #8125 merges; c9d07a9 and cc254d7 merge #8125's heads. FR8 keeps main's text from #10160 in this pull request's table. Red first: 791357d (a touch 90 ms late, 3 of 3 red before the frame watch).

Touch to first grown frame, Chromium phone profile: 170–173 ms at normal speed, 164–182 ms at 4× CPU, and 166–167 ms with a 60 ms input lag (183 ms, at 1.0110, before the frame watch). Each first frame shows the spring's one-frame value, 1.0033, after an exact 1.000. A quick tap starts no animation and writes no style. The touch handlers' cost does not change: 1.50 / 1.85 ms at 1× (1.50 / 1.70 ms before) and 6.60 / 7.95 ms at 4× (6.70 / 8.30 ms before).

A lift between the commit and 735 ms shows the menu 2–3 ms into the lift's own dispatch in WebKit (17 ms before #8125's commit-time mount). The lift proof, d44956d, asserts that dispatch, not a time. Under Reduce Motion, the 735 ms proof also wants the show inside its deciding task (4edb900).

App-level size, eager JS gzip of the example-vite page in a Vite production build, #8125's head 69e6dd8 to 8fb305f: no change as shipped; +14.7 KB with one ContextMenu; +1.2 KB with one ContextMenu and one Switch. lazy-sheet-bundle.test.mjs keeps #8208's sheet and rideKeyboard on demand; ContextMenu's row now expects the animator eager, since its growth needs the animator at the touch.

Local checks at an earlier head, cc254d7, which adds only #8125's two record sentences: check:knowledge. At c9d07a9: ContextMenuLongPress in Chromium (21 passed, 1 skipped) and in WebKit under the CI profile (17 passed, 5 skipped). At 343df06, which differs only in #8125's 735 ms proof: core typecheck; the ContextMenu, Layer and DropdownMenu units (1,135 passed); CI=true pnpm lint (0 errors); check:knowledge. At the stack's top on the same main: the ui suite (22,774 passed; DropdownMenuLazySheet's keyboard-open case failed once under load and passes alone on main and here) and test:node (17,446 passed; the one failure is the WebKit leg budget, see #8133). Earlier versions of this description said CI=true pnpm lint, but those runs were pnpm lint, which reports @eslint-react/dom-no-flush-sync only as a warning; that claim is corrected here.

The Android run at an earlier head, cc254d7 (Chrome with Gboard; normal, 4× CPU and animations removed): the growth starts 151 ms after the landing (164 ms at 4×), with no back-dated start, and peaks at 1.0746; an early lift returns in 314 / 327 ms and the return as the menu closes takes 297 / 307 ms; with animations removed there is no growth and no return. Earlier, at 0c316d9 (Chrome with Gboard; normal ×2, 4× CPU ×2 and animations removed ×2), whose ContextMenu files this head carries apart from one lint-directive comment line (it also carries main's own changes): the growth animation's start time is 152–165 ms after the landing, and it starts from 1.000 (first frames 1.000, then 1.002–1.005), with no back-dated jump. Both returns take about 285–354 ms, and Reduce Motion shows no growth. At 63c7a6c, an earlier head, the animation was created about 238 ms after the landing with a back-dated start, so its first painted frame was already at 1.016–1.039 after an exact 1.000.

Public and internal API delta

Public: none.

Internal, new:

  • privateAttributes/contextMenuTrigger.stylex.ts: the owner module of the private data-astryx-context-menu-trigger attribute (architecture:public-component-api INV23), named in component:ContextMenu's Ownership. markContextMenuTrigger(trigger) marks a trigger while its touch press feedback is attached and returns the call that unmarks it; closestContextMenuTrigger(element) finds the trigger a press belongs to. Usage: const unmark = markContextMenuTrigger(trigger);.
    Why: a press on a nested trigger grows only that trigger. Existing ones considered: data-astryx-pressable, which also sits on every pressable control inside a trigger. So the nearest one can be a nested control, not the trigger. A module-level set of mounted triggers came before; the mark replaces it.
  • ContextMenu/contextMenuPressFeedback.ts: useContextMenuPressFeedback, the trigger's growth (component:ContextMenu FR12), used only by the touch layer. Usage: const feedback = useContextMenuPressFeedback(triggerRef, visualRef, isShown, isFollowedRef, landedAtRef, followsFingerRef);.
    Why: the growth's timing is ContextMenu's own. It starts 150 ms after the landing the recognizer records, holds while the menu is up, and plays UIKit's springs. Existing ones considered: core's press machine (utils/pressFeedback.ts), whose press mark the hook reads for a pen's press. The hook watches that mark through the shared mutation observer (utils/sharedMutationObserver.ts), scoped to the trigger. An engineering owner's decision on a callback from the press path instead is pending.

Lines, over #8125's head: product +544/−17 (contextMenuPressFeedback.ts +360, ContextMenu.tsx +59/−5, ContextMenuTouchLayer.tsx +57/−4, motion/uikit/contextMenu.ts +35/−8, privateAttributes/contextMenuTrigger.stylex.ts +27, contextMenuPress.ts +6), tests +2,155/−122, records +39/−34, docs +19/−4, story +119/−1, changesets +1/−1.

@vercel

vercel Bot commented Nov 3, 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 Nov 11, 2026 4:50pm UTC

Request Review

@astracat-bot

astracat-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Nov 11, 2026, 5:04 PM UTC

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Nov 3, 2026
@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Nov 3, 2026
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Nov 3, 2026
@cixzhang

cixzhang commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 578373e

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

Current run: The current CI run concluded skipped. Current analysis is unavailable.


Generated by PR Enrichment workflow | View current CI run

@astracat-bot astracat-bot Bot 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.

Changes requested. The spec side is settled: an engineering owner approved ContextMenu FR11 at this head. Each item below still needs a fix before this can merge.

  • The growth and its early-lift return read the generic --duration-medium, --duration-fast and --ease-move tokens, which spec:AST-094 FR4 does not allow for touch motion ("MUST be a motion role named for its use"), so amend FR4 in this pull request or move the growth onto named motion roles.
  • If isDisabled turns true while a finger-opened menu is up, the touch layer unmounts and disposes its animator without restoring the trigger's inline scale, so the trigger stays about 7% larger after the menu closes (contextMenuPressFeedback.ts, the unmount cleanup).
  • ContextMenu now has two outside-press paths, the shared watchOutsidePress for touch and its own mousedown Effect with a different shadow-root rule for everything else, so extend the shared watch to close at the press for a mouse and a pen and delete the Effect (architecture:react-component-runtime INV16).
  • useContextMenuPressFeedback runs its own attach, replace and dispose lifecycle for its animator and press observer, so move both onto useAttachment, which the same layer's useGestureRecognizer already uses.
  • The outside-press changeset says "No component passes these options yet", but ContextMenu passes all three at this head, so reword it.
  • The diff against main is 4,013 authored lines because it carries #9814 and #8125, so land those first or base this branch on #8125 to leave this pull request's own 1,915 lines.

A few smaller comment and wording notes are inline.

[Automated review]

Comment thread .changeset/outside-press-pan-watch.md Resolved
@vjeux
vjeux force-pushed the uikit/c20-contextmenu-growth branch from 578373e to 0d1fdb7 Compare November 3, 2026 22:56
@cixzhang

cixzhang commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 0d1fdb7

@astracat-bot astracat-bot Bot 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.

Changes requested. The spec side is settled: an engineering owner approved ContextMenu FR11, and that approval still covers this head.

  • The growth and its early-lift return play the generic --duration-medium, --duration-fast and --ease-move tokens, which spec:AST-094 FR1 and FR4 do not yet allow for touch motion, so rebase after #9706 lands and cite its FR45 in FR11, or amend FR1 and FR4 here.
  • ContextMenuTouchLayer subscribes to its recognizer's transitions from an Effect whose only dependency never changes, so add that listener in the recognizer factory passed to useGestureRecognizer and delete the Effect.
  • ContextMenuPressRecognizer gains an onPressStart option that only a unit test passes, and its doc says the press feedback starts from it, so remove the option.
  • The diff against main is 4,132 authored lines because it carries #9814 and #8125, so land those first, or declare the stack with one identical Stack: #9814 → #8125 → #9816 line in all three descriptions.

Thanks for fixing the stuck trigger scale and moving the press feedback onto useAttachment. The changeset and the story still say the trigger "springs back" after an early lift, which FR11 now times as a curve on the theme's tokens.

[Automated review]

@vjeux
vjeux force-pushed the uikit/c20-contextmenu-growth branch from 0d1fdb7 to caf271c Compare November 4, 2026 09:37
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Nov 4, 2026
@cixzhang

cixzhang commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

/approve-spec caf271c

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Nov 4, 2026

@astracat-bot astracat-bot Bot 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.

Changes requested for this stack: #8125 is ready, and #9814 and #9816 each need one fix. A pull request lands only when it and every pull request below it are approved.

#9814: changes requested

  • Miss: the title and .changeset/outside-press-pan-watch.md say the watch can now "judge a press inside a shadow root", but a press inside an open shadow root was already judged through the event's composed path and every new test uses a closed root, so please say "closed shadow root" in both.

#8125: approved

Approved pending CI: CI has not run on this head yet, and it lands after #9814.

#9816: changes requested

  • Miss: PressTiming and timedCurve in contextMenuPressTiming.ts, and themeTiming in contextMenuPressFeedback.ts, copy ThemeTiming, timedCurve and themeTimingOf from SegmentedControl/useSegmentedSelectionMotion.ts, so please move them into one shared motion module that both components call.
  • Non-blocking: ContextMenu FR11 at this head no longer cites the measurement behind the 400 ms commit and 735 ms display, or the tests that verify them, which #8125's FR11 did.

Whole stack

The combined change adds no public API and follows spec:AST-094 and ContextMenu FR11, but it passes only once the two fixes above land.

[Automated review]

Comment thread .changeset/outside-press-pan-watch.md Resolved
@vjeux
vjeux force-pushed the uikit/c20-contextmenu-growth branch from b168c85 to 037a530 Compare November 4, 2026 10:36
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Nov 4, 2026
@cixzhang

cixzhang commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 037a530

A finger that crosses 11.5 px at 650 ms while the page is blocked past
the menu's moment, until 760, 800 or 900 ms, ends the hold when its
coalesced move arrives, as separate moves do. A still finger behind a
block gets its menu once no move comes. Every case fakes the page's
clock from 0, and the on-time browser case checks that the moment's
own timer task shows the menu.
When the menu's timer runs more than a frame late, the page was
blocked, and the finger's moves from before the moment may still be
queued. The menu then waits up to 50 ms, counted from when the page
runs again, so those moves are judged at their own times first. On an
idle page the timer runs on time, and nothing waits. The record's FR16
gap line states the shift and its 50 ms bound.

@astracat-bot astracat-bot Bot 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.

Changes requested. The component:ContextMenu FR12 record change is owner-approved at this head, and the menu now opens at 735 ms after an on-time move, which fixes the last review's first item; three items remain.

  • On a busy page, a late coalesced move whose earlier sample drifts past 11.5 px before 735 ms still opens the menu, because the arena runs the 735 ms timer for the move's last sample before followHold judges the earlier samples, and isBehindInput sees no recent move after a still hold (GestureArena.ts:360, contextMenuPress.ts:176-195 and :259-270), against spec:AST-094 FR16; run timers only up to each sample's time (or judge the samples before the moment), treat a moment timer that runs late as behind, and add both cases beside the waiting ones.
  • spec:AST-094 DEC-27 needs the Chrome-with-Gboard Android emulator takes (normal animations, animations removed, a slow-device profile) at the head under review, and the description says this head still needs them.
  • The Stack: #8125 → #9816 → #8133 → #8201 line does not hold, because #8133's head still merges this branch's previous head, so this pull request is judged alone and its 6,068 authored lines get no stack exemption; restack #8133 and #8201 onto this head.

Non-blocking: the ContextMenu record's End-user impact row still says "does not yet grow"; leave that gap to Current-state impact.

Automated review (maintainer link)

It brings meta#8125's fix for a page blocked past the menu's moment: a
late menu timer waits up to 50 ms for the moves queued behind the
block, so they are judged at their own times first.
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Nov 11, 2026
The press's units drive the page's time with the gesture arena's own
injected clock, the test harness's manual clock, instead of faking
performance.now(). A move can reach the page after its own time, and a
blocked page runs the menu's timer late on that clock. A blocked main
thread until 900 ms now shows the still finger's menu at 950 ms.
The press measured how late a move or its timer ran with
performance.now(). It now reads the gesture arena's injected clock,
whose new pageTime() returns the clock's own reading. In a browser
that clock is performance.now(), so nothing changes there; the units
drive it with the test harness's manual clock.

@astracat-bot astracat-bot Bot 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.

Approval waits on an engineering owner's decision on the record changes, and two items need changes.

  • The pull request adds component:ContextMenu FR12 (the growth on springs.contextMenuLift from 150 ms, the 400 ms commit and the 735 ms display) and a new spec:AST-094 "FR16 gap" line, and no engineering owner has approved them at this head; an engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner.
  • spec:AST-094 DEC-27 needs the Chrome-with-Gboard Android emulator takes (normal animations, animations removed, a slow-device profile) at the head under review, and the description says this head still needs them.
  • The Stack: #8125 → #9816 → #8133 → #8201 line does not hold, because #8133's head still merges this branch's previous head, so this pull request is judged alone and its 6,155 authored lines get no stack exemption; restack #8133 and #8201 onto this head.

The late, coalesced drift is fixed: a moment timer that runs late now waits for moves queued behind the block, and the new cases pin it. Non-blocking: the ContextMenu record's End-user impact row still says "does not yet grow"; leave that gap to Current-state impact.

Automated review (maintainer link)

It brings meta#8125's final head: the press reads the page's time from
the gesture arena's injected clock, which is performance.now() in a
browser, so nothing changes there.

@astracat-bot astracat-bot Bot 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.

Approval waits on an engineering owner's decision on the record changes, and two items need changes.

  • The pull request adds component:ContextMenu FR12 (the growth on springs.contextMenuLift from 150 ms, the 400 ms commit and the 735 ms display) and a new spec:AST-094 "FR16 gap" line (the menu can show up to 50 ms after a blocked page runs again), and no engineering owner has approved them at this head; an engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner.
  • spec:AST-094 DEC-27 needs the Chrome-with-Gboard Android emulator takes (normal animations, animations removed, a slow-device profile) at the head under review, and the description says this head still needs them.
  • The Stack: #8125 → #9816 → #8133 → #8201 line does not hold, because neither #8133's nor #8201's head contains this head, so this pull request is judged alone and its 6,147 authored lines get no stack exemption; restack #8133 and #8201 onto this head.

Moving the press's lateness checks onto the gesture arena's clock changes nothing in a browser and lets the unit cases model a blocked page directly. Non-blocking: the ContextMenu record's End-user impact row still says "does not yet grow"; leave that gap to Current-state impact.

Automated review (maintainer link)

vjeux added 11 commits November 11, 2026 01:57
The press feedback kept every mounted trigger in a module-level set to
find the trigger a press mark belongs to. Each trigger now carries the
private context-menu-trigger attribute while its press feedback is
attached, and closest() finds it. The attribute's owner module is
privateAttributes/contextMenuTrigger.stylex.ts.
The Ownership section names the private attribute's owner module, its
writer and its closest reader.
ContextMenuTouch.ts only re-exported the press recognizer for the touch
layer, its one importer. The layer now imports contextMenuPress.ts.
A finger crosses 11.5 px at 700 ms while the page is blocked from 600 ms
to 850 ms, so its move reaches the page after the menu's moment. The
hold must still end, with no menu. The case checks that the move
reached the page behind the block and within the press's 50 ms wait.
AST-094's current state says why the press keeps a 50 ms wait beside
the arena's one-task yield, and names the arena's page time and the
touch fields the press reads. The FR16 gap line stays as it was. Layer
runtime's outsidePress owner line names ContextMenu's watch and its
pressTypes option.
The record now links motion/uikit/contextMenu.ts, the context menu's
measured UIKit table, by path. Its public-contract line says that an
omitted presentation means the touch model under a coarse pointer,
with presentation="popover" as the way back, instead of no change.
A ContextMenu rendered into a same-origin iframe gets a 120 ms tap
stamped on that window's clock. It must open no menu and keep its
click. The gesture host reads each document's own clock since the
second-document clock fix.
component:ContextMenu's Ownership names contextMenuPress.ts and
ContextMenuTouchLayer.tsx: the touch hold's recognizer, and the layer
that attaches it and opens the menu. Records only, no code.

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

Approval waits on an engineering owner's decision on the record changes, and five items need changes.

  • The pull request adds component:ContextMenu FR12 (the growth on springs.contextMenuLift from 150 ms, the 400 ms commit and the 735 ms display) with new Ownership and path-applicability lines, plus spec:AST-094's "FR16 gap" and "Two waits" lines, and no engineering owner has approved them at this head; an engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner.
  • Regression: since main's per-document gesture clock, a ContextMenu in a same-origin iframe or Picture-in-Picture window starts its growth in the first frame and dates it on the page's clock, because watchFramesFrom in contextMenuPressFeedback.ts reads the page's performance.now() and frames while the onset is on the trigger's own document clock.
  • privateAttributes/contextMenuTrigger.stylex.ts is a new internal shared module that no current record names, so it needs an engineering owner's API decision (approving the Ownership line at this head records it).
  • Newly live check: motion/uikit/contextMenu.ts is a new internal shared module that no current record names, so it needs the same owner decision (the path-applicability line).
  • spec:AST-094 DEC-27 needs the Chrome-with-Gboard Android emulator takes (normal animations, animations removed, a slow-device profile) at the head under review, and the description says this head still needs them.
  • The Stack: #8125 → #9816 → #8133 → #8201 line does not hold because #8201's head does not contain this head, so this pull request is judged alone and its 6,328 authored lines get no stack exemption; restack #8133 and #8201 onto this head.

The unit suites this change reaches pass at this head, and a desktop right-click renders the same menu as main. Non-blocking: drop "does not yet" from the record's End-user impact row, and the stopOnsetRef comment restates the name.

Automated review (maintainer link)

* trigger a press belongs to. No package entry exports it.
*/

const CONTEXT_MENU_TRIGGER = 'data-astryx-context-menu-trigger';

ghost Nov 11, 2026

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.

This is a new internal shared module (imported by ContextMenu) that no current record names, so it needs an engineering owner's API decision that says why existing modules are not enough. The proposed Ownership line in component:ContextMenu names it, so an engineering owner's /approve-spec for this head settles it.

* Every leaf has one or more `@source`. A GUESS value also has the `@measure`
* task that confirms or replaces it. uikit.test.ts enforces both.
*/
export const contextMenu = {

ghost Nov 11, 2026

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.

Newly live check: this is a new internal shared module (imported by ContextMenu's modules) that no current record names, so it needs an engineering owner's API decision. The proposed path-applicability block in component:ContextMenu names it, as component:Switch names motion/uikit/switch.ts, so the same /approve-spec settles it.

Christopher Chedeau added 7 commits November 11, 2026 08:07
The touch layer's header read as if any second finger ended the hold.
The recognizer fails on a lift, a second finger or a scroll only
before the commit. The timeline's header, the spec's header and one
spec comment named a press feedback, its growing copy and its scales,
which this code does not have. Comments only.
The press feedback ships here. So the timeline's header names it, its
growing copy and its scales, and the long-press spec's header and its
scroll helper name it again. Comments only.
A ContextMenu in a same-origin iframe or a Picture-in-Picture window
gets its touches on that window's clock. Its growth must start in the
first frame at or after that clock's onset, 150 ms after the landing.
The gesture host times each document's touches on its own window's
clock, so a trigger in a same-origin iframe or a Picture-in-Picture
window reports its landing on that clock. The press feedback still
watched the page's frames and clock, so the growth started in the first
frame, dated on the page's clock. It now reads the trigger's window for
its frames and clock. For another window, its animator runs on that
window's clock and frames; the page's own document keeps the defaults.
A comment that only restated a name goes.
The touch grow and the trigger lift are a gap that Current-state impact
states against FR5. So the End-user impact row drops its "does not yet"
sentence. Records only.
The block repeated what the hook's and the touch layer's JSDoc say.
One line stays: only the recognizer's failure or cancel returns the
growth. Comment only.

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

Approval waits on an engineering owner's decision on the record changes, and four items need changes.

  • The pull request adds component:ContextMenu FR12 (the growth on springs.contextMenuLift from 150 ms, the 400 ms commit and the 735 ms display) with new Ownership and path-applicability lines, plus spec:AST-094's "FR16 gap" and "Two waits" lines, and no engineering owner has approved them at this head; an engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner.
  • privateAttributes/contextMenuTrigger.stylex.ts is a new internal shared module that no current record names, so it needs an engineering owner's API decision (approving the Ownership line at this head records it).
  • motion/uikit/contextMenu.ts is a new internal shared module that no current record names, so it needs the same owner decision (the path-applicability line).
  • spec:AST-094 DEC-27 needs the Chrome-with-Gboard Android emulator takes (normal animations, animations removed, a slow-device profile) at the head under review, and the description says this head still needs them.
  • The Stack: #8125 → #9816 → #8133 → #8201 line does not hold because #8201's head contains none of the pull requests below it, so this pull request is judged alone and its 6,406 authored lines get no stack exemption; restack #8133 and #8201 onto this head.

The growth in an iframe or Picture-in-Picture window now starts at that window's own onset, and its new case fails before the fix. The unit suites this change reaches pass, and a desktop right-click renders the same menu as main.

Automated review (maintainer link)

This branch was successfully deployed

1 active deployment
Preview — 8fb305f8 Deployed Nov 11, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) ci:run-a11y Run the accessibility suite on this PR; write access, next push (AST-030 FR12) CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review needs:spec-owner-review Current knowledge records await owner approval

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants