Skip to content

feat(core): one progress module for the motion foundation - #9755

Merged
cixzhang merged 2 commits into
mainfrom
motion/progress-module
Nov 3, 2026
Merged

cixzhang merged 2 commits into
mainfrom
motion/progress-module

Conversation

@cixzhang

@cixzhang cixzhang commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Supersedes #8497 by @vjeux, which stays the author of this commit. #8497's branch carries the whole open chain under #8360, about 27,000 lines against main. This module needs nothing from that chain: everything it imports (Animator, MotionCurve and MotionProperty from motion/animator; solveSpring, toSpring, SETTLING_EPSILON and SpringDescription from motion/spring) is already on main. So this pull request is #8497's own change alone, rebuilt on main.

Maintainer impact

Motions that move several properties along one path (a popover growing out of its button while it fades in, a scrim's dim following a sheet) all need the same four pieces of math. Without one module, each presentation keeps its own copy. Several open motion pull requests build on this one.

Intended invariant

No behavior change. The module is internal, and no component imports it yet. It follows spec:AST-094 FR3 and FR6, with one velocity unit throughout: progress per second.

Change

A new internal module, motion/progress.ts, with its unit test, a README row and a [chore] changeset. animatedLayout's private springProgress now reads progressCurve instead of solving the same spring itself, so one implementation remains (same curve, same 0.001 settle epsilon and duration):

  • progressCurve(spring, {velocity, epsilon}): a spring's progress from 0 to 1 as a closed-form curve, exactly 1 once it settles;
  • mapProgress(driver, f): a curve that follows another through f;
  • mapRange(input, output, {clamp}): the clamped linear map most companions use;
  • presentedProgressVelocity: the progress velocity that continues what an animator shows.

Evidence

  • Before: none of these exist on main; feat(core): one progress module for the motion foundation #8497 can't merge as it stands (it carries an open chain).
  • After: tsc is clean against current main, and the 12 progress tests pass.
  • Product/runtime behavior verified unchanged: animatedLayout's settle curve is the same spring, read as 1 − progress; the motion, List and TreeList suites pass (1,042 tests).

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

  • pnpm exec vitest run packages/core/src/motion/progress.test.ts: 12 passed.
  • Motion, useAnimatedLayout, List and TreeList suites: 1,042 passed.
  • pnpm exec tsc -p packages/core --noEmit: clean.
  • Strict ESLint on the two source files, pnpm check:knowledge, pnpm check:changesets: pass.
  • pnpm review:pre: 0 findings; 335 authored lines.

Co-authored-by: Cindy Zhang (rebuilt on main for the motion foundation stack).

progressCurve, mapProgress, mapRange and presentedProgressVelocity in
motion/progress.ts, internal, one velocity unit (progress per second).
Rebuilt on main from the pull request's own change, without the open
dependency chain it no longer needs.

Co-authored-by: Cindy Zhang <cindyxz@meta.com>
@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 3, 2026 3:32pm 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
🟢 Reviewed (for maintainers only) Nov 3, 2026, 3:35 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 commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · 3cfb3b1

Storybook · Sandbox · built from this PR's head

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

Requesting changes: the module looks correct, and its 12 tests, tsc and ESLint pass, but it adds a second copy of math the motion folder already has.

Blocking

  • progressCurve (progress.ts:56–70) duplicates the private springProgress in animatedLayout.ts:144–151 (same solve, same 0.001 settle epsilon, same exact landing, identical output), so please build springProgress on progressCurve or replace it, leaving one implementation.

Non-blocking

  • The file header (progress.ts:3–39) runs well past the roughly 12-line header limit and repeats the mapProgress and presentedProgressVelocity JSDoc; trimming it to @file, @input, @output, @position and SYNC: would fix both.
  • The STEP comment (progress.ts:49) restates the name, and mapProgress steps the driver's own units rather than a progress, so a clearer name such as FINITE_DIFFERENCE_STEP can replace it.

[Automated review]

epsilon = SETTLING_EPSILON,
}: {velocity?: number; epsilon?: number} = {},
): MotionCurve {
const curve = solveSpring(toSpring(spring), {from: 0, to: 1, velocity});

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 the same construction as the private springProgress in packages/core/src/motion/animatedLayout.ts:144–151 (unit move, settleTime(0.001), exact landing); for the settle spring and three others the two agree bit for bit. Please build springProgress on progressCurve (durationMs = Math.max(1, progress.duration * 1000), remaining = t => 1 - progress.at(t / 1000)) or replace it, so one implementation remains.

type SpringDescription,
} from './spring';

/** The step of the finite differences taken on a progress. */

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.

Non-blocking: the comment restates the name, and mapProgress takes this step on the driver's own value (px for a sheet's y), not on a progress. Suggest renaming to FINITE_DIFFERENCE_STEP and dropping the comment.

Comment on lines +21 to +35
* An interrupted motion continues from what is on screen: the
* block reads the presented values and velocities from the animator at one
* `now` (`get`, `getVelocity`), restarts a path from those values, and starts
* its progress with the presented velocity projected on the path's tangent
* at its start, v·t / (t·t), where t = d(values)/dp. Channels are weighted
* into one unit first (px per unit of scale, say), so a scale and a
* translation count alike.
*
* A companion follows a driving curve through `mapProgress`, most often
* with `mapRange`: `mapProgress(sheetY, mapRange([closedY, openY], [0, dim]))`
* dims a scrim as a sheet rises. Played on another animator, it stays in
* lockstep only when it starts with its driver: pass the driver's start,
* `scrim.play({opacity}, {at: start})` with `start = await handle.started`
* (or one shared `at` for both calls); two calls from rest otherwise each
* start at their own commit.

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.

Non-blocking: these paragraphs take the header to 37 lines (the convention is about 12) and repeat the JSDoc of presentedProgressVelocity and mapProgress. Suggest keeping @file, @input, @output, @position and SYNC:, and moving the lockstep sentence into the mapProgress JSDoc.

…ressCurve

CodeBunny on #9755: springProgress solved the same spring as progressCurve.
It now reads progressCurve (remaining = 1 - progress), so one implementation
remains. Same curve, same settle epsilon (0.001) and duration.

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

Recommending approval: springProgress in animatedLayout.ts now reads progressCurve, so one spring-progress implementation remains, and the glide's curve is unchanged (same duration, every frame within 1.1e-16 of the move).

Non-blocking

  • The progress.ts file header (lines 3–39) still runs well past the roughly 12-line header limit and repeats the mapProgress and presentedProgressVelocity JSDoc; trimming it to @file, @input, @output, @position and SYNC: would fix both.
  • The STEP comment (progress.ts:49) restates the name and describes a step on a progress, while mapProgress steps the driver's own units; a name such as FINITE_DIFFERENCE_STEP can replace it.

[Automated review]

@cixzhang
cixzhang marked this pull request as ready for review November 3, 2026 15:39
@cixzhang cixzhang added the ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) label Nov 3, 2026
@cixzhang
cixzhang marked this pull request as draft November 3, 2026 15:41
@cixzhang
cixzhang marked this pull request as ready for review November 3, 2026 15:41
@cixzhang
cixzhang merged commit 7585c6e into main Nov 3, 2026
129 of 162 checks passed
@github-actions
github-actions Bot deleted the motion/progress-module branch November 4, 2026 06:04

This branch was successfully deployed

1 active deployment
Preview — 3cfb3b10 Deployed Nov 3, 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) 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