Repository navigation
Conversation
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>
…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.
…shows at each progress lerpPath, edgeGrowPath, each path's restartFrom and morphCurves in motion/morphPath.ts, internal. Pure geometry over a progress curve: the caller chooses the timing. Rebuilt on the progress module from the pull request's own change, without the open chain it no longer needs. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
# Conflicts: # packages/core/src/motion/README.md
… source rect createRectMorph in motion/rectMorph.ts, internal: present along a path and dismiss back onto a copy or the element, each continuing the other from the rect on screen. Rebuilt on main from the pull request's own change and ported to main's engine (createMotionAnimator). Timing follows the owner's rule for touch motion: present and dismiss take a motion, either a theme duration and easing (a tap, a key, the program) or a spring (a gesture's release, from its velocity). timedProgressCurve joins the progress helpers. architecture:react-component-runtime INV12 records the implementation gap: the size-change glide moves onto the morph next. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
/approve-spec 131ca14 |
|
AI review status for this pull request.
|
There was a problem hiding this comment.
Approval waits on an engineering owner's decision about the rect morph's timing rule, and one fix is needed.
rectMorph.ts documents and implements a rule no current record states (a present or dismissal that a tap, a key or the program starts plays the theme's duration and easing from rest, and only a gesture's release plays a spring), which contradicts AST-094 FR31, where a touch menu grows on springs.menuOpen, and FR6, where a new target keeps the current velocity. An engineering owner either records the rule in AST-094 (amending FR31 and FR6) and approves that edit by commenting /approve-spec <full head SHA> on this pull request (the author may do so as an engineering owner), or the morph carries the presented velocity into a timed retarget and its example and changeset follow FR31.
Blocking
- A dismissal onto a copy calls
attach(null)on a handed-in animator (rectMorph.ts:448), and neitherreset()nordispose()attaches it again, so the owner's ownsetandplaystop reaching the element; attach a handed-in animator again and add a test that the owner's write lands after a copy dismissal.
Everything else matches the current records, and the motion suites, the typecheck and the Chromium interaction spec pass.
[Automated review]
…opy dismissal CodeBunny on #9769: a dismissal onto a copy detached the animator, and a handed-in one was never attached again, so its owner's writes stopped reaching the element. Only the morph's own animator lets go now. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
There was a problem hiding this comment.
Approval waits on an engineering owner's decision about the rect morph's timing rule, and two fixes are needed whatever that decision is.
The morph documents and implements a rule no current spec states: a present or dismissal that a tap, a key or the program starts plays the theme's duration and easing from rest, and only a gesture's release plays a spring. That contradicts AST-094 FR31, where a touch menu grows on springs.menuOpen, and FR6, where a new target keeps the current velocity. To settle it, an engineering owner either records the rule in AST-094 (amending FR31 and FR6) and approves that edit by commenting /approve-spec <full head SHA> on this pull request (the author may do so as an engineering owner), or the morph carries the presented velocity into a timed retarget and its example and changeset follow FR31.
Blocking
timedProgressCurve(packages/core/src/motion/progress.ts:79-99) is a second copy of the timed curvesolveAnimatedPageScrollalready provides inpaging.ts, without that curve's velocity carry, so move that curve with its optional velocity intoprogress.tsand call it from the pager and the morph.- The INV12 edit needs the
## Writing rubric resultssection in the description and should end at "its own compositor translate.)" instead of the plan "it moves onto the morph next", as the knowledge-contracts writing rules require.
The handed-in animator now stays on the element through a copy dismissal, reset() and dispose(), and the motion suites, the typecheck and the Chromium interaction spec pass.
[Automated review]
…er and the morph CodeBunny on #9769: solveAnimatedPageScroll already played this curve and keeps the velocity of the move it retargets. timedCurve in progress.ts is now that one curve; the pager and the morph's timed motion call it, so a timed present that interrupts a dismissal keeps its velocity. The INV12 note states the system only. File header and comments trimmed. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
|
/approve-spec e3a43d0 |
There was a problem hiding this comment.
Approval waits on an engineering owner's decision about the rect morph's timing rule, and one fix is needed whatever that decision is.
The RectMorphMotion doc, its example and the changeset say that a present or dismissal a tap, a key or the program starts plays the theme's duration and easing, and only a gesture's release plays a spring; no current spec states that rule, and it contradicts AST-094 FR31, where a menu opened under a finger grows on springs.menuOpen, and FR4, where touch motion reads its named roles. To settle it, an engineering owner either records the rule in AST-094 (amending FR31 and FR4) and approves that edit by commenting /approve-spec <full head SHA> on this pull request (the author may do so as an engineering owner), or the doc, the example and the changeset name the presentation's motion roles instead.
Blocking
- A timed present or dismissal that starts at rest is handed a velocity of 0 (
packages/core/src/motion/rectMorph.ts:351and:397-399), whichtimedCurvetreats as a retarget and uses to cancel the easing's starting slope, so a 250 ms present on the entrance curve is 48 px wide at 50 ms where the easing gives 114 px; pass no velocity when nothing is in flight, and test a present from rest on an easing that does not start flat, such as[0, 0, 0.58, 1], against the easing itself.
A timed retarget now keeps its velocity, the duplicated timed curve is gone, and the motion and pager suites and the typecheck pass.
[Automated review]
CodeBunny on #9769: with nothing in flight the morph handed timedCurve a velocity of 0, which it treats as a retarget and uses to cancel the easing's starting slope. Only a motion in flight hands its velocity on now; tests check a present from rest and a dismissal from a settled present against the easing itself. Header and comments shortened. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
There was a problem hiding this comment.
Approval waits on an engineering owner's decision about the rect morph's timing rule; nothing else blocks.
- The
RectMorphMotiondoc (packages/core/src/motion/rectMorph.ts:109-115), its example (:214-215, a tapped menu on the theme's entrance duration and easing) and the changeset say that a present or dismissal a tap, a key or the program starts plays the theme's duration and easing, and only a gesture's release plays a spring; no current spec states that rule, and it contradicts AST-094 FR31, where a menu opened under a finger grows onsprings.menuOpenand shrinks onsprings.menuClose, and FR4, where touch motion reads the role named for its use.
To settle it, an engineering owner either records the rule in AST-094 (amending FR31 and FR4) and approves that edit by commenting /approve-spec <full head SHA> on this pull request (the author may do so as an engineering owner), or the doc, the example and the changeset name the presentation's motion roles instead. The earlier /approve-spec comments name earlier heads, so the INV12 note needs that same exact-head approval.
A timed motion from rest now plays the easing it is given, and the new tests fail without the fix. Non-blocking: the description's Testing line still says the unit suite has 35 cases including a timed present that ignores a given velocity; rectMorph.test.ts has 24 cases and the one at line 276 proves the opposite.
[Automated review]
The type's doc, the example and the changeset name no rule of their own about which input plays which timing: each play takes the timing the adopting component's motion record names (AST-094 FR45). Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
|
/approve-spec 3272828 |
|
/approve-spec 33a3ee8 |
A popover's grow path read its opacity on a straight line, so before its start and past its end it went below 0: a turn-around toward the source read -0.025 at 1.05. An opacity below 0 draws as 0, so opacityAt now stops at 0. The popover proofs compare opacities as drawn, since Popover's own model still reads below 0 there.
The anchored menu's case modeled a straight line from a 22 px square on its control. The menu now grows out of its trigger on Popover's edge path, which the popover cases already check, so the case no longer describes any presentation's model. The file header follows.
|
/approve-spec 6b6310c |
There was a problem hiding this comment.
Approved pending CI: CI's heavy suites have not run on this head yet; they run in the re-run before merge.
createRectMorph matches the current specs: it is the internal shared rectangle morph architecture:layer-runtime INV15 names, each play keeps the velocity on screen (spec:AST-094 FR6) and takes whichever timing the adopting component's motion record names (FR45), and the INV12 and AST-094 record edits carry an engineering owner's approval for this head. The motion unit suites and the morph's Chromium browser proof pass on this head.
Non-blocking:
- Popover still plays its own copy of the edge growth (
Popover/popoverSurfaceMotion.ts);morphPath.test.tspins it toedgeGrowPath, and moving Popover onto the morph removes the copy. - Two comment suggestions (INV29) are inline.
#8485 does not contain this head yet, so the stack line does not hold right now and this pull request was reviewed on its own, the morph paths included.
[Automated review]
| * @output Exports `progressCurve`, a spring's progress from 0 to 1 as a | ||
| * MotionCurve. Exports `easedProgress`, a duration and easing that keeps a | ||
| * retargeted velocity, and `timedCurve`, that curve on values. Exports | ||
| * `mapProgress`, one curve as a function of another, and `mapRange`, the | ||
| * clamped linear map most companions use. Exports | ||
| * `presentedProgressVelocity`, the progress velocity that continues what | ||
| * an animator shows |
There was a problem hiding this comment.
Non-blocking (INV29): this header now runs 39 lines, and this pull request rewrites its @output. Keep @input, @output, @position and SYNC:. Move the velocity-projection paragraph to presentedProgressVelocity's JSDoc and the companion paragraph to mapProgress's.
| // A spring goes through `resolveMotion`. A motion role resolves to its | ||
| // table value, and a bare spring comes back as given. `velocity` is | ||
| // undefined when nothing was moving: a timed motion then plays its easing | ||
| // as given. |
There was a problem hiding this comment.
Non-blocking (INV29): the first two sentences restate the call below. Suggested: "With no motion in flight, velocity is undefined, so a timed motion plays its easing as given."
…and the rect morph use timedCurve easedProgress moves to the progress helpers byte for byte, so Popover and SegmentedControl read the same positions and velocities as on main. timedCurve is the pager's curve as on main, with one change: from rest, its velocity at t <= 0 is the curve's own starting slope. The rect morph plays its timed motion on timedCurve from 0 to 1. callerMotionValues.test.ts pins easedProgress and the pager on a fixed grid, bit for bit, to main's values. Based on vjeux's test (vjeux/astryx coverage/rect-morph-first-instant)
… speed A linear move of 300 px over 0.3 s, retargeted at t = 0, reads 0 px/s on main, so the retarget starts from rest. Now it reads about 1000 px/s, the curve's own starting slope, and the retarget keeps that speed. The position stays continuous. Based on vjeux's test (vjeux/astryx coverage/rect-morph-first-instant)
On main a 0 ms animated scroll reads NaN at t = 0 and its start position before it. timedCurve's 0 ms guard puts it on its target with no speed, as the rect morph's 0 ms timing does.
|
/approve-spec 9d40852 |
There was a problem hiding this comment.
Changes requested: one blocking finding.
packages/core/src/motion/__tests__/callerMotionValues.test.ts:60pins theeasedProgressgrid to617cb8ab38270e58, butmain'seasedProgressand this branch's both producee0dc2c4f1f0daee6on that grid, so the new test fails; pin the valuemain's code actually produces, or compare against a frozen copy ofmain's function.
The rest matches the current specs (architecture:layer-runtime INV15, spec:AST-094 FR6 and FR45), the INV12 and AST-094 edits carry an engineering owner's approval for this head, and the morph's Chromium browser proof passes. Non-blocking: INV12 now states its condition twice (drop "once Core has that morph; until then…" and keep the gap sentence), and two INV29 comment suggestions from the earlier review still apply. The stack line does not hold (no listed pull request contains all the others), so this pull request was reviewed on its own, the morph paths included.
| through the shared rectangle morph (`architecture:layer-runtime` INV15; | ||
| `spec:AST-094` FR5) once Core has that morph; until then, the module plays | ||
| its own compositor translate. It never animates height. Its walk visits only | ||
| its own compositor translate. (Implementation gap: Core has the shared |
There was a problem hiding this comment.
Non-blocking (records carry no temporary status): now that Core has the morph, "once Core has that morph; until then, the module plays its own compositor translate" is sequencing that the gap sentence already covers. Suggested: "…played through the shared rectangle morph (architecture:layer-runtime INV15; spec:AST-094 FR5). (Implementation gap: the size-change glide still plays its own compositor translate.)"
…functions A float64 hash of easedProgress's values is not the same on every platform: macOS arm64 and CI's Linux give different hashes for main's code. The test now keeps copies of easedProgress and solveAnimatedPageScroll as they are on main (ebb9dde), and checks that the shipped functions give the same number, bit for bit (Object.is), for each position and velocity on the grid. From rest, the pager's velocity at t <= 0 stays the one value left out. Based on vjeux's test (vjeux/astryx coverage/rect-morph-first-instant)
|
/approve-spec 6c17632 |
There was a problem hiding this comment.
Approved pending CI: CI's heavy suites have not run on this head yet; they run before merge.
createRectMorph and its paths are the shared rectangle morph the layer runtime calls for, follow the touch-motion timing and velocity rules, and the record edits are owner-approved for this head. The caller-value test now compares the shipped easedProgress and pager curve with copies of main's functions, bit for bit, so it holds on any platform.
Three optional cleanups from the earlier inline notes still stand: the long progress.ts header, the comment at rectMorph.ts:317-320 that restates its code, and INV12's "once Core has that morph; until then" clause, which the new gap sentence makes redundant.
…ect-morph # Conflicts: # packages/core/src/SegmentedControl/useSegmentedSelectionMotion.ts
There was a problem hiding this comment.
Approval waits on an engineering owner's decision about one record edit. No code change is needed.
- This head adds the shared rectangle morph to Core. Current
architecture:react-component-runtimeINV12 says the size-change glide plays through that morph once Core has it. The pull request records the glide as an implementation gap instead, and that record edit, with thespec:AST-094path link, has no engineering owner's approval 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. If the gap is not accepted, the glide needs to play throughcreateRectMorphin this pull request.
The morph's unit suite, the comparison of the existing callers' values and the Chromium browser proofs pass at this head. The open inline suggestions on header length, one comment and the INV12 wording are non-blocking.
Supersedes #8329 by @vjeux, who stays the author of this commit. Stacked on #9764 (the morph paths) and targets
main. #8329's branch carries the open chain under #8909, #8340 and #8360, about 30,000 lines. This pull request is #8329's own change, rebuilt onmainand ported tomain's engine (createMotionAnimator; #8340 was absorbed intomain).Stack: #9764 → #9769 → #8485 and #8080 (#8078 and #8508 have merged).
Behavior module:
packages/core/src/motion/rectMorph.ts, withpackages/core/src/motion/progress.ts; every other file supports them.Maintainer impact
Five touch presentations grow a surface out of what the finger touched and shrink it back: menus, Popover, the lifted preview, Lightbox's zoom, and a sub-menu's parent card. Without one module, each would write its own version and get interruptions and Reduced Motion right on its own.
Intended invariant
No behavior change: the module is internal, and no component imports it yet.
spec:AST-094FR45), as ruled by Specs & Decisions.present(path, motion)anddismiss(path, motion, {target})take aRectMorphMotion: either{duration, easing}or{spring}, whichever the adopting component's motion record names. The morph has no per-trigger rule of its own. Either starts at the velocity it is given, by default the presented velocity, so a new target keeps the current velocity (spec:AST-094FR6). The doc example takes the timing as given.easedProgressmoves toprogress.tsbyte for byte, so Popover, SegmentedControl and the layout glide read the same positions and velocities as onmain.timedCurveissolveAnimatedPageScroll's curve as onmain(the Hermite carry that keeps a retargeted velocity), with these two changes:mainreads 0.mainit reads NaN at t = 0. This matches the rect morph's 0 ms timing (spec:AST-094FR5).timedCurvefrom 0 to 1, so its timed motion keeps a retargeted velocity and reads its velocity with the finer central difference.architecture:react-component-runtimeINV12 says the size-change glide plays through the shared rectangle morph once Core has it. Now that Core has it, INV12 records an implementation gap: the glide still plays its own compositor translate. Moving it onto the morph is a separate, later pull request.Change
motion/rectMorph.ts, internal:createRectMorphpresents along a path (lerpPath,edgeGrowPath, or a caller's own) and dismisses back onto a target the caller gives (a copy, or the element). Each interrupts the other from the rect on screen. Nothing stays inline once a present settles, and Reduced Motion is the animator's.motion/progress.ts:easedProgress, moved unchanged fromthemeTiming.ts, andtimedCurve, moved frompaging.ts, a timed motion on values that keeps a retargeted velocity.themeTiming.ts, Popover and SegmentedControl importeasedProgressfrom there.solveAnimatedPageScrolland the morph calltimedCurve.architecture:react-component-runtimeINV12: the implementation-gap sentence above.rectMorph.test.ts: 24 cases, including a timed present that keeps its velocity) and the browser interaction spec. A README row and a[chore]changeset.coverage/rect-morph-first-instant):motion/__tests__/callerMotionValues.test.tskeeps copies ofmain'seasedProgressandsolveAnimatedPageScroll(ebb9ddec). On a fixed grid it checks that the shipped functions give the same number, bit for bit (Object.is), for each position and velocity. The only value it leaves out is the pager's from-rest velocity at t ≤ 0. The check runs on whatever platform runs the test, so it holds wherever that platform's math library differs.progress.test.tsholdseasedProgress's 1 ms slope step: an ease-in reads 0.2053 progress/s, and[0.2, 0, 0, 1]reads 0.9761, at t = 0.1 ms on a 0.1 s curve.paging.test.tsadds a retarget at the first instant (0 → about 1,000 px/s, with the position continuous) and a 0 ms move.Evidence
createAnimator, whichmaindoesn't have.tscis clean, and the motion, Popover, SegmentedControl and pager suites pass (1,459 tests).main's code, the first-instant and 0 ms pager tests fail. On this pull request's earlier head (6b6310c6), theeasedProgressslope test and both reference comparisons fail.main(ebb9ddec). Each hash is the first 16 hex digits of the SHA-256 of the raw float64 values. The grid samples each curve at 1/200 of its length, plus 10 µs, 0.1 ms, 0.5 ms and 1 ms from each end. It covers nine cubic-bézier easings,steps(4)and each caller's fallback timing. Popover's channels are sampled through its ownsurfaceCurvesmapping, and SegmentedControl's through its lens mapping.main= headmain= headeasedProgress2939ddc076992be9966708df1d2d58ae7ddf1d33716be387b952c22259b2f795f41cbbd9f678b98b6ac217e2a392ecc5ab5cff1a2d9edd29e0758e6000b52217maincode, CI's Linux gives a differenteasedProgresshash. What counts is thatmainand head match on one platform, and the test checks exactly that on CI.Scope
Testing
pnpm exec vitest run --project ui packages/core/src/motion packages/core/src/Popover packages/core/src/SegmentedControl packages/core/src/hooks/usePager --maxWorkers=1: 1,459 passed.scripts/snap-paging-bundle.test.mjs: 4 passed.pnpm exec tsc -p packages/core --noEmit: clean. Strict ESLint on the changed files,pnpm check:knowledgeandpnpm check:changesets: pass.rectMorph.interaction.browser.spec.ts) runs in CI's interaction job.Writing rubric results
Record changed:
architecture:react-component-runtimeINV12, one implementation-gap sentence.#number,/pull/or/issues/.