Skip to content

chore(lab): Lab's animator hook, over Core's internal touch entry - #9754

Open
vjeux wants to merge 1 commit into
meta:mainfrom
vjeux:c09/core-touch-entry-lab-animator
Open

vjeux wants to merge 1 commit into
meta:mainfrom
vjeux:c09/core-touch-entry-lab-animator

Conversation

@vjeux

@vjeux vjeux commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Stack: #9754 → #9699 → #9700 → #9966

Maintainer impact

Lab's NavigationStack (#9696), PageControl (#9669) and TabBar (#9699) animate with Core's motion engine, listen for pans with Core's gesture hook and pan recognizer, stretch past their ends with Core's rubber band, and read the writing direction as Core does. Lab reaches Core's internals only through the internal @astryxdesign/core/internal/touch entry, which lacks these names today. Internal entries land with their first user, so this PR leaves draft together with the first of them.

Intended invariant

architecture:react-component-runtime INV13: Core and Lab components reach the shared touch modules directly only for its known public-API gaps, through @astryxdesign/core/internal/touch (spec:AST-017 FR21), and no internal factory shares a public factory's name (spec:AST-107 FR6).

Change

The entry re-exports, by name, code already on main. Each name fills an INV13 gap:

  • createMotionEngine (the motion engine's factory, renamed from createMotionAnimator at its source and in every internal caller, so it differs from the public createAnimator by more than one word) with its types MotionEngine, MotionEngineOptions and MotionCurve: the animator's jobs beyond the public handle (a closed-form curve played with retargets, a companion on its clock, a chosen start, the Reduce Motion substitution);
  • useTouchPresentation and TOUCH_QUERY: the touch presentation query;
  • useGestureRecognizer and PanGestureRecognizer: custom recognizer relationships on the gesture host;
  • rubberBand: the rubber band;
  • UIKIT_GESTURES: UIKit's measured gesture tables;
  • isRtlElement: the direction read Core's own gestures use, which has no public equivalent.

Lab gains internal/useMotionEngine, a stable motion engine bound to an element through a callback ref, which those components share. No root re-export, no docs, and the @astryx/no-core-internal-import lint rule already limits the entry to Core and Lab.

Evidence

  • Failure before the change: the entry exports none of these names, so a Lab component cannot import them.
  • Success after the change: the entry's test checks each name is the same value Core imports by path, and that no public factory's name (createAnimator, createPanGesture, createDragToSnapPoints) is on the entry. useMotionEngine's tests bind, rebind and release its element.
  • Product/runtime behavior verified unchanged: re-exports and a mechanical rename of the engine's factory (56 references in 16 Core files and a script); no behavior changes.

Scope

  • No intended public API, product behavior, visual direction, or policy change.
  • Product changes discovered during the work were removed or split.
  • Public text and artifacts contain no internal Meta context.

Testing

At 3946ad2, on main 36cde5e, with node 24.22.1 from .nvmrc: pnpm review:pre (0 findings); the unit tests of every area the rename touches (motion, animator, Switch, SegmentedControl, Slider, usePager, DateInput, the internal entry and Lab's hook: 63 files, 1,894 tests) and the motion-node-load and touch-support bundle script tests (11) pass; strict ESLint over the whole repo (0 errors); the full build, then the whole typecheck (0 errors); the repository checks (knowledge records, export sync, changesets and their coverage, the i18n catalog and runtime checks, CLI API types, token docs, Lab readiness) pass; pnpm api:report shows nothing from this PR (its only line is main's own CodeBlock startLineNumber prop); the UI tests for the changed set (120 files, 3,725 tests), the node tests (2) and the animator's interaction spec in Chromium (18) pass. A whole-repo git grep -E 'createMotionAnimator|useMotionAnimator' at this head: 0 remaining hits.

Stack: #9754 → #9669 → #9684

@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 4, 2026 4:13am UTC

Request Review

@astracat-bot

astracat-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 4, 2026, 4:20 AM 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 commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · 3946ad2

Storybook · Sandbox · built from this PR's head

Current run: The current CI run concluded failure. 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

Choose a reason for hiding this comment

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

Changes requested. The new internal-entry names and the Lab hook fit the runtime and release rules, but two things need fixing before this can merge:

  • packages/core/src/internal/touch.ts:48 re-exports createAnimator from ../motion/animator, which main has since renamed to createMotionAnimator, so once merged Core's typecheck and build fail (TS2305) and useMotionAnimator throws createMotionAnimator is not a function; rebase, export createMotionAnimator directly, and compare against animator.createMotionAnimator in touch.test.ts:44.
  • The changeset files this internal-entry change under [experimental], which Core's changelog lists as an Experimental API even though the entry is outside the public API and no Lab component uses these names yet; use [chore] Internal: … as earlier internal-entry changesets do.

The identity check at touch.test.ts:44 also passes when both sides are undefined, so an added typeof entry.createMotionAnimator === 'function' assertion would catch this kind of break.

[Automated review]

Comment thread packages/core/src/internal/touch.ts Outdated
export {isRtlElement} from '../hooks/isRtlElement';
// The animator's internal factory, under a name no public factory has: the
// public animator factory is `createAnimator`.
export {createAnimator as createMotionAnimator} from '../motion/animator';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

main has renamed this factory to createMotionAnimator and no longer exports createAnimator from ../motion/animator, so after a merge this line fails with TS2305 and the Lab hook gets undefined. After rebasing:

export {createMotionAnimator} from '../motion/animator';

The alias comment above can then go.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4e363d8: rebased onto main, and the engine's factory is now createMotionEngine at its source and in every internal caller, so the entry exports it directly with no alias.

expect(entry.useGestureRecognizer).toBe(hook.useGestureRecognizer);
expect(entry.useTouchPresentation).toBe(presentation.useTouchPresentation);
expect(entry.isRtlElement).toBe(direction.isRtlElement);
expect(entry.createMotionAnimator).toBe(animator.createAnimator);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Once rebased, compare against animator.createMotionAnimator. Also assert expect(typeof entry.createMotionAnimator).toBe('function'): merged with main today, both sides are undefined, so this check passes while the build fails.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 4e363d8: the identity check compares animator.createMotionEngine, and a loop asserts every re-exported function is a function and every table is defined.

'@astryxdesign/core': patch
---

[experimental] Lab components reach Core's animator, cubic-bézier solver, UIKit timing and gesture tables, gesture hook, pan recognizer, rubber band, touch query, touch-presentation hook and writing-direction read through Core's internal `@astryxdesign/core/internal/touch` entry, which only Astryx's own packages may import and which stays outside the public API and its compatibility promise.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[experimental] puts this under "Experimental APIs" in Core's changelog, but the internal entry is outside the public API and no Lab component imports these names yet. Suggested:

[chore] Internal: Core's internal touch entry re-exports the animator, curve and gesture helpers Lab's touch components use. Apps import nothing new.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 4e363d8: the changeset is now [chore] Internal: …, with "Apps import nothing new."

@vjeux
vjeux force-pushed the c09/core-touch-entry-lab-animator branch from e8cb6cb to 4e363d8 Compare November 3, 2026 14:58
vjeux added a commit to vjeux/astryx that referenced this pull request Nov 3, 2026
This head's touch.test.ts against the base's touch.ts (meta#9754's entry): the
identity and defined checks for the new names fail here and pass on the
parent.

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

Recommending approval. Each new name on Core's internal touch entry fills a known internal-API gap, the createMotionEngine rename is complete and changes no behavior, and the earlier alias break and changeset category are fixed.

Two optional follow-ups:

  • packages/core/src/internal/touch.ts: the header's @output still says "the gesture core's primitives"; it could name the gesture, motion, touch-presentation and direction modules the entry now carries.
  • The description says the useMotionEngine tests bind, rebind and release the element, but none replaces the node; a re-keyed child case would back that.

[Automated review]

Comment thread packages/core/src/internal/touch.ts Outdated
* @input The gesture core: the arena, the recognizers, and their geometry
* @input The gesture core and the motion, touch and direction modules Lab
* uses
* @output `@astryxdesign/core/internal/touch`: the gesture core's primitives

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: @input now names the motion, touch and direction modules, but @output still says "the gesture core's primitives". Suggested:

 * @output `@astryxdesign/core/internal/touch`: the gesture, motion,
 *   touch-presentation and direction modules Lab uses, re-exported by name

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 3946ad2: @output now reads "the gesture, motion, touch-presentation and direction modules Lab uses, re-exported by name".

Internal entries land with their first user, per meta#8562. Lab's
PageControl, NavigationStack and TabBar animate with Core's motion
engine, listen for pans with Core's gesture hook and pan recognizer,
stretch past their ends with Core's rubber band and read the writing
direction as Core does, so `@astryxdesign/core/internal/touch` re-exports,
by name:

- the motion engine, `createMotionEngine`, for its jobs beyond the public
  `createAnimator` (architecture:react-component-runtime INV13), with its
  types `MotionEngine` and `MotionEngineOptions` (the engine's `Animator`
  and `AnimatorOptions`) and `MotionCurve`. The engine's factory is renamed
  from `createMotionAnimator` at its source and in every internal caller,
  so the engine and the public animator differ by more than one word;
- `useGestureRecognizer`, `useTouchPresentation` and `isRtlElement`;
- `PanGestureRecognizer`, `rubberBand`, UIKit's `UIKIT_GESTURES` table and
  `TOUCH_QUERY`, which more than one of those components reads.

Named re-exports of code already on main, plus that rename: no behavior
changes, no docs, no root re-export, and the `@astryx/no-core-internal-import`
lint rule already limits the entry to Core and Lab. The entry's test checks
it re-exports the same values Core imports by path, and that it carries no
public factory's name.

One Lab module that those components share comes with them:
`internal/useMotionEngine`, a stable motion engine bound to an element
through a callback ref.
@vjeux
vjeux force-pushed the c09/core-touch-entry-lab-animator branch from 4e363d8 to 3946ad2 Compare November 4, 2026 04:04
@vjeux

vjeux commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

The entry now re-exports only the 11 names that Lab's touch components read. cubicBezier, timing, AnimateOptions and MotionHandle left this PR, and they land with their first user (NavigationStack's Core part, #9696). Here is who reads each name that stays:

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

Recommending approval, pending CI: CI has not run on this head yet. It skips drafts, and because the branch is in a fork, a maintainer adds the ci:run label so CI runs once this is ready for review.

Every new name on @astryxdesign/core/internal/touch is one the internal-entry rules allow, the createMotionEngine rename is complete and merges cleanly with main, and nothing public or user-visible changes.

Optional:

  • #9669, next in the declared stack, is built on this branch's previous commit, so it needs a rebase onto this head before the stack lands together.
  • useMotionEngine.test.tsx covers mount, unmount and a stable ref but no node replacement, which the description lists as "rebind".
  • The header of packages/core/src/internal/touch.ts runs 20 lines, past the source-comment guide of about 12 (see the inline note).

[Automated review]

Comment on lines +5 to +8
* @input The gesture core and the motion, touch and direction modules Lab
* uses
* @output `@astryxdesign/core/internal/touch`: the gesture, motion,
* touch-presentation and direction modules Lab uses, re-exported by name

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 (source-comment guide): this header now runs 20 lines, past the "about 12 lines at most" the guide sets for @input, @output, @position and SYNC:. These two tags fit on one line each:

 * @input The gesture core and Core's motion, touch and direction modules
 * @output `@astryxdesign/core/internal/touch`: those modules for Lab, by name

@position can also drop what the code already shows (named re-exports, Core's relative imports); keep its record citations until a record links this file.

@cixzhang

cixzhang commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 3946ad2

@vjeux
vjeux marked this pull request as ready for review November 4, 2026 16:25

This branch was successfully deployed

1 active deployment
Preview — 3946ad2e Deployed Nov 4, 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.

2 participants