Repository navigation
spec(AST-107): public gesture hooks — usePanGesture, useDragToSnapPoints, useAnimator - #8600
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
|
/approve-spec fed2e1c |
PR Analysis Report
|
There was a problem hiding this comment.
Changes requested: the direction in spec:AST-107 is approved, but four places in the record need fixing before it can govern an implementation.
- FR2 names the internal function
resolveSnapPoint, which a requirement may not prescribe and which does not exist onmain; state the release rule as behavior and giveflickThreshold's default and unit. - FR3's "default motion role" matches no defined motion role; name the role it means.
snapPointsreuses BottomSheet's option name, whose numbers are viewport fractions, without stating its own unit.- FR3 returns an
animatorthat the FR4 verification row says must fail as an exported animator object; give the public object its own name and signatures.
The positions docs in FR5 need #8601 to land first, and after these edits the new head needs a fresh /approve-spec.
[Automated review]
|
|
||
| const {settleTo} = useDragToSnapPoints(ref, { | ||
| axis: 'y', | ||
| snapPoints: [0, 240, 480], |
There was a problem hiding this comment.
snapPoints shares BottomSheet's public option name, where a number is a viewport fraction in (0, 1], so 0, 240 and 480 would all be ignored there. Could FR2 state this option's unit and origin, or use a different name?
| - **FR2 — `useDragToSnapPoints` settles an element on snap points.** | ||
| `useDragToSnapPoints(ref, {axis, snapPoints, flickThreshold?, onSettle})` | ||
| lets a drag move the element along `axis`, and at the release settles it on | ||
| the snap point the release resolves to (`resolveSnapPoint`): a release faster |
There was a problem hiding this comment.
resolveSnapPoint is an internal function, and architecture:knowledge-contracts INV18 keeps internal function names out of requirements unless they are public contracts (the Non-goals keep the resolver internal). It is also not on main yet. Suggest stating the rule as behavior, citing spec:AST-094 FR37 for the moving-back cancel, and giving flickThreshold's default and unit.
| `{ref, animator}`, where `animator` has `animate`, `decay`, `set`, `get`, | ||
| `getVelocity`, and `stop` for the one element `ref` attaches to. Reduce | ||
| Motion is planned for every animation (`design:reduced-motion` DR1–DR3). | ||
| Every animation plays on the default motion role; choosing a spring or curve |
There was a problem hiding this comment.
No current record or code defines a "default motion role": spec:AST-094 FR4 names each role for its use, and none is a default. Which role should useAnimator play? The FR3 verification row depends on the answer.
| | FR1 | Unit and browser tests of `usePanGesture` | a drag on each axis, a cancel, a press, a long press | a recognizer in the result, a missing state, a drag off its axis, or a press or long press reported as a pan fails | | ||
| | FR2 | Unit and browser tests of `useDragToSnapPoints` | a slow release, a flick past `flickThreshold`, a reversed drag, `settleTo` from a key | a settle on the wrong snap point, a reversed drag that settles elsewhere, or a missing `onSettle` fails | | ||
| | FR3 | Unit and browser tests of `useAnimator` | each method, Reduce Motion, an unattached ref | a missing method, a spring other than the default role, or motion that ignores Reduce Motion fails | | ||
| | FR4 | Export check of `@astryxdesign/core` and its hooks | the public exports | a recognizer, arena, animator object, or resolver reachable from a public export fails | |
There was a problem hiding this comment.
Under architecture:public-component-api INV12, the animator that useAnimator() returns is reachable from a public export, so this row fails FR3 as written. Suggest giving the public object its own name, with signatures for decay, set, get, getVelocity and stop, and rewording this row and the Non-goal to mean the internal animator module.
…ator's public handle and its motion role
|
/approve-spec 92becf1 |
…speed; animate follows the theme's motion knobs
|
/approve-spec 1d9b484 |
|
A naming question on FR3, facts first. The experimental touch layer in #8076 already has an internal hook named |
|
A question on FR1 and FR4 before code. AST-107 gives a product a pan as values, but no way for that pan to order itself against a component's own recognizers. Two cases it leaves open:
UIKit covers both with |
There was a problem hiding this comment.
Changes requested: the direction in spec:AST-107 is approved for this head, but several claims rest on records that are not on main yet or conflict with current ones. Rebase once #8613 and #8601 land, or drop the clauses that depend on them, resolve the flickThreshold conflict, and then have an engineering owner re-approve the new head with /approve-spec.
- FR3's theme-following springs and fixed decay cite
spec:AST-108, which is not onmain, whilespec:AST-094FR4 and DEC-11 still say a theme cannot change a motion role. - FR2's public
flickThresholdconflicts withspec:AST-100FR3 andspec:AST-094FR4, which keep gesture thresholds internal, so either drop it or amend those records with thespec:AST-002FR5 admission argument. - The default "1008, the sheet's measured flick speed" has no source on
main, and FR5 citesspec:AST-103for apositionsoption that record does not define yet. - The FR3 verification row tests an unattached ref, but FR3 never says what happens then.
[Automated review]
| `springs.stateChange` motion role (`spec:AST-094` FR4), which follows the | ||
| theme's tempo and character (`spec:AST-108` FR3), and `decay` on the fixed | ||
| momentum decay (`spec:AST-108` FR4); choosing any other spring or curve is |
There was a problem hiding this comment.
spec:AST-108 is not on main yet, and current spec:AST-094 FR4 says "The roles are internal until the public motion-token set is decided, so a theme cannot override them yet (DEC-11)". Could this land after #8613, which adds AST-094 DEC-15, or defer spring choice to the public motion-token set as the earlier wording did?
| except that a release faster than `flickThreshold`, in pixels per second | ||
| (default 1008, the sheet's measured flick speed), carries to the next snap point in its direction, and a |
There was a problem hiding this comment.
spec:AST-100 FR3 keeps flick thresholds as "per-component tuning that stays internal (spec:AST-002 FR3)", and spec:AST-094 FR4 says gesture thresholds "are not public concepts". Either drop the option and use the measured flick speed internally, or amend those records here with the AST-002 FR5 cases. Separately, main has no sheet flick speed of 1008: the sheet projects the throw with UIKit's 0.998 deceleration rate. The default therefore needs a source, as AST-094 FR2 requires.
| motion modules stays internal (`architecture:public-component-api` INV21). | ||
|
|
||
| - **FR5 — Each hook ships with docs and a playing-cards example.** Each public | ||
| API here, and `createReorderable`'s `positions` (`spec:AST-103`), ships with |
There was a problem hiding this comment.
spec:AST-103 FR1 on main lists only onReorder and isReorderable as createReorderable options, and positions arrives with #8601. Land that first, or drop this clause and the fanned hand until it does.
| | -------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| | FR1 | Unit and browser tests of `usePanGesture` | a drag on each axis, a cancel, a press, a long press | a recognizer in the result, a missing state, a drag off its axis, or a press or long press reported as a pan fails | | ||
| | FR2 | Unit and browser tests of `useDragToSnapPoints` | a slow release, a flick past `flickThreshold` and the default flick speed, a release moving back, `settleTo` from a key | a settle on the wrong snap point, a reversed drag that settles elsewhere, or a missing `onSettle` fails | | ||
| | FR3 | Unit and browser tests of `useAnimator` | each method, Reduce Motion, an unattached ref | a missing method or channel, an `animate` off `springs.stateChange`, or motion that ignores Reduce Motion fails | |
There was a problem hiding this comment.
FR3 does not say what an unattached ref does, or what an interrupted animate promise does, so this row can neither pass nor fail. spec:AST-002 DEC-8 asks a public primitive's contract to name its failure behavior.
…the public layer (FR6, FR7, DEC-2); recognizers use a kind field
|
/approve-spec 1f22c68 |
|
/approve-spec ab539aa |
|
That The gate only accepts the exact current head, so post this instead: A new commit invalidates the command, so repeat it after any push. |
|
/approve-spec 687ec4c |
There was a problem hiding this comment.
Requesting changes: an engineering owner approved the direction of spec:AST-107 for this revision, but three clauses still rest on records main does not have yet. Please land #8613, #8646 and #8601 first and rebase (or drop the clauses that depend on them), make the small record edits below, and have an engineering owner re-approve the new revision with /approve-spec.
- FR3's theme-following spring cites
spec:AST-108, whilespec:AST-094FR4 onmainstill says a theme cannot override a motion role (DEC-11). - FR1 and FR2's mouse-and-pen scope cites AST-094 FR43, which
mainlacks, while the INV13 text this pull request keeps attaches no recognizer under a fine primary pointer. - FR5 and FR7 cite
createReorderable'spositionsand attach path, whichspec:AST-103does not define yet. - The Public contract should name entry points for
createContextMenuanduseContextMenu, FR2 should say which order a sortedsnapPointsindex counts in, and FR8 should state focus and arrow-key behavior in the factory's own menu.
[Automated review]
| createPanGesture, | ||
| createDragToSnapPoints, | ||
| createAnimator, | ||
| } from '@astryxdesign/core'; |
There was a problem hiding this comment.
The Public contract has no entry point for createContextMenu and useContextMenu (FR8, lines 178–190). Add them here so FR4's export check knows where they belong.
| `usePanGesture(ref, {axis?, onPan})` calls `onPan` with | ||
| `{state, translation, velocity}` as a drag on the element begins, changes, | ||
| ends, or is cancelled. `axis` limits the drag to `'x'` or `'y'`. A finger, | ||
| a mouse, and a pen all drive it (`spec:AST-094` FR43). It never |
There was a problem hiding this comment.
AST-094 on main ends at FR41 (#8646 proposes FR43), and the INV13 text this pull request keeps says "Where the primary pointer is fine, no recognizer attaches and no touch listener is registered". Land #8646 first and rebase, or scope FR1 and FR2 to what INV13 and AST-094 FR19 allow today and drop the FR43 citation.
| to different snap points, which no motion role or | ||
| theme knob expresses (`spec:AST-002` FR5); Core components keep theirs | ||
| internal (`spec:AST-100` FR3). Under reduced motion the element goes to its snap point | ||
| without travel (`design:reduced-motion` DR1–DR3). `snapPoints` is sorted before use; an empty list, |
There was a problem hiding this comment.
Sorting before use means an unsorted list's indexes differ from the caller's. Please say whether onSettle(index) and settleTo(index) count in the given order or the sorted one.
| `stop()` halts them where they are. Reduce Motion is planned for every | ||
| animation (`design:reduced-motion` DR1–DR3). Every `animate` plays on the | ||
| `springs.stateChange` motion role (`spec:AST-094` FR4), which follows the | ||
| theme's tempo and character (`spec:AST-108` FR3), and every `decay` plays on |
There was a problem hiding this comment.
spec:AST-108 is not on main yet (#8613 is open), and spec:AST-094 FR4 there still says "so a theme cannot override them yet (DEC-11)". Land #8613 first and rebase, or have animate play springs.stateChange as AST-094 FR4 defines it, cite AST-094 FR3's momentum coast for decay, and drop the AST-108 citations until then.
| use it rather than the internals beneath it. A component reaches internals | ||
| only for a job the public layer does not expose; those jobs are listed as | ||
| known public-API gaps in `architecture:react-component-runtime` INV13. | ||
| `createReorderable`'s plain-element attach path (`spec:AST-103`) covers |
There was a problem hiding this comment.
spec:AST-103 FR1 on main gives createReorderable only onReorder and isReorderable, applied through a collection's reorderable prop; the plain-element attach path (and FR5's positions, line 192) arrive with #8601. Land it first, or drop both clauses until then.
| - **FR8 — A headless context menu.** `createContextMenu(element, {items | render, label?, isDisabled?, | ||
| onOpenChange?, presentation?}) → {open(at?), close(), destroy()}` | ||
| attaches a context menu to any element: it opens on the element's context | ||
| request (a long press, a right click, or Shift+F10), at `at` when given; `render(container)` |
There was a problem hiding this comment.
FR8 says how the menu opens and that label names it, but not where focus starts, how arrow keys move through the factory's own items rows, or where focus returns; component:ContextMenu, which the options defer to, is draft. Please state it, or cite the current record that defines it.
| | FR3 | Unit and browser tests of `useAnimator` | each method, Reduce Motion, an unattached ref, an unknown channel, a call after `destroy` | a missing method or channel, an `animate` off `springs.stateChange`, or motion that ignores Reduce Motion or a throw on an unknown channel or after `destroy` fails | | ||
| | FR4 | Export check of `@astryxdesign/core` and its hooks | the public exports | a recognizer, the arena, the internal animator module, or the resolver reachable from a public export fails | | ||
| | FR6 | Unit tests of each factory and its hook, server-render and import checks, and listener counts after `destroy` | each factory without React; each hook; a server import; `destroy` mid-drag | a hook with behavior its factory lacks, a listener left after `destroy`, or DOM work at import fails | | ||
| | FR7 | Source boundary check over Core and Lab against the gap table | every Core and Lab component that drags, snaps, animates one element, reorders, or animates layout | a component reaching internals for a job the public layer covers, or an internal use whose job is not in the gap table, fails | |
There was a problem hiding this comment.
INV13 lists the known public-API gaps as prose (react-component-runtime.md, INV13); there is no gap table. Name INV13's list here.
|
/approve-spec 32814b9 |
There was a problem hiding this comment.
Requesting changes: an engineering owner approved the direction of spec:AST-107 for this revision, but three clauses still rest on records main does not have yet. Please land #8613, #8646 and #8601 first and rebase (or drop the clauses that depend on them), then have an engineering owner re-approve the new revision with /approve-spec.
- FR3's theme-following spring cites
spec:AST-108, whilespec:AST-094FR4 onmainstill says a theme cannot override a motion role (DEC-11). - FR1 and FR2's mouse-and-pen scope cites AST-094 FR43, which
mainlacks, while the INV13 text this pull request keeps attaches no recognizer under a fine primary pointer. - FR5 and FR7 cite
createReorderable'spositionsand attach path, whichspec:AST-103does not define yet.
[Automated review]
| `usePanGesture(ref, {axis?, onPan})` calls `onPan` with | ||
| `{state, translation, velocity}` as a drag on the element begins, changes, | ||
| ends, or is cancelled. `axis` limits the drag to `'x'` or `'y'`. A finger, | ||
| a mouse, and a pen all drive it (`spec:AST-094` FR43). It never |
There was a problem hiding this comment.
| `stop()` halts them where they are. Reduce Motion is planned for every | ||
| animation (`design:reduced-motion` DR1–DR3). Every `animate` plays on the | ||
| `springs.stateChange` motion role (`spec:AST-094` FR4), which follows the | ||
| theme's tempo and character (`spec:AST-108` FR3), and every `decay` plays on |
There was a problem hiding this comment.
| use it rather than the internals beneath it. A component reaches internals | ||
| only for a job the public layer does not expose; those jobs are listed as | ||
| known public-API gaps in `architecture:react-component-runtime` INV13. | ||
| `createReorderable`'s plain-element attach path (`spec:AST-103`) covers |
There was a problem hiding this comment.
spec:AST-103 FR1 on main gives createReorderable only onReorder and isReorderable, applied through a collection's reorderable prop; this attach path, and FR5's positions, arrive with #8601. Land it first, or drop both clauses until then.
|
/approve-spec 738ca2c |
There was a problem hiding this comment.
Requesting changes: an engineering owner approved spec:AST-107 for this head, and the FR43 and positions dependencies are now resolved on main, but two clauses still contradict current records. Please either land #8613 first and update this branch, or have FR3's animate play springs.stateChange as spec:AST-094 FR4 defines it and drop the spec:AST-108 citations. Then carry the flickThreshold exception into spec:AST-100's Non-goals and FR3 verification row, and have an engineering owner re-approve the new head with /approve-spec.
- FR3 cites
spec:AST-108, which is not onmain, whilespec:AST-094FR4 still says a theme cannot override a motion role yet (DEC-11). spec:AST-100still says every threshold stays internal, and its FR3 row fails "a threshold reachable from public API".
[Automated review]
| `springs.stateChange` motion role (`spec:AST-094` FR4), which follows the | ||
| theme's tempo and character (`spec:AST-108` FR3), and every `decay` plays on | ||
| the fixed momentum decay (`spec:AST-108` FR4); choosing any other spring or |
There was a problem hiding this comment.
| stays internal (`spec:AST-002` FR3), except the public `flickThreshold` a | ||
| product passes to `spec:AST-107`'s snap-point drag; a component never derives a second |
There was a problem hiding this comment.
The same exception is missing from this record's Non-goals (line 51: "every threshold stay internal") and from the FR3 verification row (line 124), which fails on "a threshold reachable from public API". Could both carry the flickThreshold exception too?
| | FR3 | Unit and browser tests of `useAnimator` | each method, Reduce Motion, an unattached ref, an unknown channel, a call after `destroy` | a missing method or channel, an `animate` off `springs.stateChange`, or motion that ignores Reduce Motion or a throw on an unknown channel or after `destroy` fails | | ||
| | FR4 | Export check of `@astryxdesign/core` and its hooks | the public exports | a recognizer, the arena, the internal animator module, or the resolver reachable from a public export fails | | ||
| | FR6 | Unit tests of each factory and its hook, server-render and import checks, and listener counts after `destroy` | each factory without React; each hook; a server import; `destroy` mid-drag | a hook with behavior its factory lacks, a listener left after `destroy`, or DOM work at import fails | | ||
| | FR7 | Source boundary check over Core and Lab against the known public-API gap list in `architecture:react-component-runtime` INV13 | every Core and Lab component that drags, snaps, animates one element, reorders, or animates layout | a component reaching internals for a job the public layer covers, or an internal use whose job is not in the gap table, fails | |
There was a problem hiding this comment.
INV13 holds a prose list, not a table, so the failure signal should say "gap list", as the Verification cell does.
|
/approve-spec c635592 |
There was a problem hiding this comment.
Requesting changes: an engineering owner approved spec:AST-107 for this head and the flickThreshold exception is carried through spec:AST-094 and spec:AST-100, but FR3 still contradicts a current record. Please either land #8613 first and update this branch, or have FR3's animate play springs.stateChange as spec:AST-094 FR4 defines it, cite AST-094 FR3's momentum coast for decay, and drop the spec:AST-108 citations, the Non-goal's included. Then have an engineering owner re-approve the new head with /approve-spec.
- FR3 says
animatefollows the theme's tempo and character (spec:AST-108FR3), butspec:AST-108is not onmain, andspec:AST-094FR4 still says a theme cannot override a motion role yet (DEC-11).
[Automated review]
| `springs.stateChange` motion role (`spec:AST-094` FR4), which follows the | ||
| theme's tempo and character (`spec:AST-108` FR3), and every `decay` plays on | ||
| the fixed momentum decay (`spec:AST-108` FR4); choosing any other spring or |
There was a problem hiding this comment.
spec:AST-108 is not on main yet (#8613 is open), and spec:AST-094 FR4 there still says “a theme cannot override them yet (DEC-11)”. Please land #8613 first and update this branch, or have animate play springs.stateChange as AST-094 FR4 defines it, cite AST-094 FR3's momentum coast for decay, and drop the spec:AST-108 citations here and in the Non-goal at line 58.
|
/approve-spec c635592 |
…ooks # Conflicts: # docs/specs/AST-094-touch-presentations/spec.md
|
/approve-spec e2455c8 |
There was a problem hiding this comment.
Approving: an engineering owner approved spec:AST-107 for this head, and with spec:AST-108 now on main, every current record the spec relies on exists, so FR3's theme-following animate no longer conflicts with spec:AST-094 FR4. Two optional clarifications would keep the first implementation from settling details of a stable API by accident:
- FR1 and FR2: say whether
axis: 'x'offsets and the flick direction follow the inline direction under right-to-left (design:bidirectionalityDR13). - FR1: name the values of
onPan'sstateand whethertranslationandvelocityare points or scalars alongaxis.
[Automated review]
|
|
||
| - **FR1 — `usePanGesture` reports a drag as values.** | ||
| `usePanGesture(ref, {axis?, onPan})` calls `onPan` with | ||
| `{state, translation, velocity}` as a drag on the element begins, changes, |
There was a problem hiding this comment.
Non-blocking: could FR1 name state's values and say whether translation and velocity are points or scalars along axis? With no _experimental suffix, the first implementation would otherwise fix them for a stable API.
| `useDragToSnapPoints(ref, {axis, snapPoints, flickThreshold?, onSettle})` | ||
| lets a drag move the element along `axis`, under | ||
| every pointer, as FR1's does. `snapPoints` are offsets in | ||
| pixels along `axis` from the element's resting position, in ascending order; |
There was a problem hiding this comment.
Non-blocking: under right-to-left, do axis: 'x' offsets and the flick direction follow the inline direction, or stay physical with the caller flipping them? design:bidirectionality DR13 governs a horizontal gesture's outcome, and spec:AST-057 FR8 and spec:AST-100 FR5 state theirs.
| `animate(target, {velocity?}) → Promise<void>` springs the given channels to | ||
| `target`; `decay(velocity) → Promise<void>` coasts them from a velocity; | ||
| `set(values)` jumps them; `get()` and `getVelocity()` read them; and | ||
| `stop()` halts them where they are. Reduce Motion is planned for every |
There was a problem hiding this comment.
Non-blocking: “Reduce Motion is planned for every animation” can read as future work; “Under reduced motion every animation follows design:reduced-motion DR1–DR3” states the same rule in the present tense.
Decided (Cindy, 2026-10-26): products get a public hook layer for gestures and motion ("option B"), with docs and a playing-cards example built on the hooks.
User impact
Product teams can build drags, snapping, and single-element motion that feel like Core's, with keyboard paths and Reduce Motion handled, without copying Core's internals. Core keeps its gesture machinery internal, so it can evolve without breaking products.
Record:
spec:AST-107public gesture hooksFR1
usePanGesture(ref, {axis?, onPan}):onPan({state, translation, velocity})with drag values only, never a recognizer object, and no press or long press.FR2
useDragToSnapPoints(ref, {axis, snapPoints, flickThreshold?, onSettle}) → {settleTo(index)}:snapPointsare pixel offsets alongaxis, not BottomSheet's viewport fractions. The element settles on the nearest snap point; a release pastflickThreshold(px/s, default 1008, the sheet's measured flick speed) carries to the next, and a release moving back returns to the start (spec:AST-094FR37).settleTois the keyboard path.FR3
useAnimator() → {ref, animator}:animatoris a publicAnimatorHandleover x, y, scale, rotate, and opacity, withanimate,decay,set,get,getVelocity, andstopsignatures. It's distinct from the internal animator module. Reduce Motion is planned.animateplayssprings.stateChange(the role modeled on the platform's default animation), which follows the theme's tempo and character (spec:AST-108FR3);decayis the fixed momentum decay (AST-108 FR4). It merges after spec(AST-108): theme motion is three knobs — tempo, character, reducedMotion #8613 and spec(AST-103): optional positions() for grid and fan drops, and attach() for plain elements #8601.FR4 one hook per job (
architecture:public-component-apiINV21):usePanGesturedoes no presses;useLongPressowns those.useAnimatoranimates one element;useAnimatedLayout(spec:AST-105) animates an area.snapPointsand Carousel'shasSnapstay the component path.FR5 done criterion:
positions(spec(AST-103): optional positions() for grid and fan drops, and attach() for plain elements #8601), ships with docsite docs: usage, options, the keyboard and AT path, Reduce Motion, a "use the component prop instead when…" note, and a live example.positions.DEC-1 records the decision.
FR6 (Cindy 20:38): non-React factories
createPanGesture(element, {axis?, onPan}) → {destroy},createDragToSnapPoints(element, {…}) → {settleTo, destroy}, andcreateAnimator(element) → {animate, decay, set, get, getVelocity, stop, destroy}. Each hook is a thin wrapper that delegates entirely to its factory (INV21: one job, two layers).destroyremoves every listener, and importing does no DOM work, so it's SSR-safe.Name clash: Core's internal
createAnimatoreither becomes the public factory (if its shape matches) or is renamed. The public name stayscreateAnimator, and no internal factory shares a public name.FR7 (Cindy 20:39), dogfood: Core and Lab use the public layer wherever it covers the job (factories and hooks,
createReorderable,useAnimatedLayout). Internals are allowed only for listed public-API gaps: recognizer relationships, the presentation hold, the input hold, press feedback, lift-from-trigger, and action-group tracking.FR5's docs cover factories and hooks, and the playing-cards demo ships in plain JS (factories) and in React (hooks).
architecture:react-component-runtimeINV13 (Cindy 20:39): the recognizer family (base, arena, host) is the one internal place using classes, with akindfield instead ofinstanceof. Everything else iscreate…factories.DEC-2 records the 20:38/20:39 decisions.
FR8 (Cindy 20:46): a public headless
createContextMenu(element, {items | render, onOpenChange?, presentation?}) → {open(at?), close(), destroy()}, which opens on the context request (long press, right click, Shift+F10), plus a thinuseContextMenu(ref, opts) → {open, close}.<ContextMenu>is built on it (INV21, dogfood). DEC-3.Implementation PRs follow once this record lands; Changesets follow the release policy (public docsite).
Round-2 record fixes
architecture:react-component-runtimeINV13 (INV18: no internal names in a system spec). FR7 points there. The internal-animator branch is dropped.architecture:public-component-apiINV21 now states that a hook which delegates entirely to a public factory is one API with it.flickThresholdis added as the one exception inspec:AST-100FR3 andspec:AST-094FR4. Its default names its source: UIKit's sheet flick speed on iOS 26.2.destroydoes nothing and resolves.Writing rubric results