Repository navigation
Conversation
What one call becomes under Reduce Motion (an opacity-only cross-fade whose travel lands while the element is transparent, or everything at once; a dip on a borrowed stylesheet opacity) moves from createAnimator's closure to reducedMotionPlan.ts, a pure planner that reads the animator through a view and returns the plan; the animator turns the plan into its segments and runs the landings. No behavior change: the animator's tests pass unchanged, and the planner has its own. Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
…ed pull requests This pull request keeps the animator's Reduce Motion plan (now its own planner module), the browser-set start of a call from rest, promotion, atRest, rebase and animatorFor. Two features it carried move up the stack, each with its own tests and changeset: - play(curves), baking any closed-form curve like a spring; - driving a pseudo-element (a dialog's ::backdrop). The changeset is split the same way. Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
play(curves) bakes any curve given as value and velocity functions of time (a spring chasing a moving target from solveSpringTowardRamp, a fitted UIKit curve) exactly like a spring: presentation values, velocities, stop, finished and the shared clock work the same, a later animate starts from the curve's value and velocity, and under Reduce Motion a curve that moves is travel the policy replaces. Tests and a changeset of its own. Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
createAnimator(element, {pseudoElement: '::backdrop'}) animates a
pseudo-element: with no inline style, a static filling animation holds
its value after a curve ends and after set; a custom property on its host
promotes it; a Reduce Motion dip borrows and hands back the
pseudo-element's own opacity, and its holds follow a node replacement.
animatorFor keeps one animator per element and pseudo-element. Tests and
a changeset of its own.
Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
…ot the page's frames around it "a harness read inside a trace is not the page's long task…" also asserted that no long task started anywhere in the read's window. On CI's runner each frame of the fixture's 1,200 running rows took 51-54 ms, the page's own long tasks, and failed it; at 4x CPU locally they take 126-220 ms. The read itself was set apart correctly. The test now asserts the read's own classification, and removes the rows before the timer, click and direct-call windows, so each holds only its own work. It passes at 4x CPU, where the previous assertion failed.
…hed curve waitForAnimation's finished branch read every recorded animation, so a scroll- or view-timeline animation that reached its end (a scroller scrolled to its end) came back as finished with a NaN duration and interval. The recorded set now keeps document-timeline animations only. The scroll-timeline self-test scrolls its scroller to the end first; it returned the NaN entry before (Chromium, 3x repeat pass). The README, JSDoc and file header name watchAnimations where they list what moves the wait window and what the file exports. Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
…rves), pseudo-element support) as a dependency # Conflicts: # packages/core/src/motion/README.md # packages/core/src/motion/animator.ts
… motion-role animator # Conflicts: # packages/core/src/motion/animator.ts
The travel list moved to the planner with the Reduce Motion plan; nothing in the animator reads it any more (no-unused-vars).
…deselect clock; the animator changeset drops its no-component clause The press profiles' README gave a control UIButton's 0.25 s release. PRESS_PROFILES releases every surface on the list's deselect clock until a control runs its own release, as its comment says; the README now says so. Its test list still credited VelocityTracker's tests with checking each strategy against the true velocity, though one filter remains: it now names what they check. The animator's changeset said no component uses it, which goes false in the release that carries the components that do.
…removed at its end is still reported - Arming the recorder now records first and stamps strictly after the newest entry: a watch taken right after a step that changed style no longer reports that step's transition (created by the arm's own style read) as started after it. - Each entry keeps its target's ancestors as recorded, so an exit whose element the page removes at its end is still reported for the container it left, instead of a wait rejecting with nothing recorded. A self-test for each; both failed before (Chromium, 3x repeat pass). Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
solveSpringTowardRamp went with play(curves)'s tests, and a coast test destructured an element it never reads (no-unused-vars).
play(curves)'s tests use solveSpringTowardRamp, so this layer keeps its import.
…he frames it credits as skipped
With a compositor record and an on-screen start, the moved criterion counted
from the first moved frame after the on-screen frame, while the gap from the
on-screen frame to it was credited as frames the screencast skipped. When the
cast skipped the frames right after the on-screen start, those credits had
no place in the denominator: four unmoved frames that fail on a full cast
("31 of 35 expected frames moved") passed as "26/30 moved and 5 skipped by
the screencast (103%)", and a clean run read 117%.
The criterion now counts the compositor's frames from the on-screen frame,
so both sides cover the same frames: the skipped cast fails like the full
one, and a clean run reads at most 100%. A busy.test.ts case pins all three.
… transparent A call with travel (or a waiting landing) whose opacity rose from a visible value landed its travel at once, in view, and one whose opacity fell to a visible value landed at the end of its fade, in view. The entrance branch now applies only from 0 and the exit branch only to 0; any other such call dips (fades out, lands at the bottom, fades back to its opacity), as design:reduced-motion DR1 and DR2 ask. The tests read the opacity at the moment the travel lands.
…equest that reads them Theme wrote data-astryx-motion-tempo and data-astryx-motion-character on its wrapper and on <html> for the animator, but nothing in this pull request reads them: the animator's reader comes with meta#8677, where the springs follow the knobs. The attributes and their three tests move there; the reduced-motion mark and its custom property stay. The comment beside the mark now says what follows it, in the words of the record's known gap.
…ll request doesn't change An earlier formatter run had rewritten untouched lines of motion.doc.mjs (quote style, the sections' indentation). They go back to main's text; the topic's own changes stay: the usage section's entrance and move curves, its sample's entrance curve, and the theme-motion section.
…ut, not the vars its motion knobs derive theme-private-vars.mjs checks what a theme's author wrote; the private vars the compiler derives are valid output. A defined theme's tokens and resolved adaptation rules are compiler output, and the motion knobs add private vars to both (spec:AST-108 FR4): --_astryx-motion-tempo for a theme with tempo, and the busy loops' pre-tempo copies for one with a duration scale. The check read them as authored, so astryx theme build reported them as errors. Fails now with 20 errors; a private var the author writes is still reported once.
…les for private vars A defined theme's tokens are resolved, with the private vars the motion knobs derive, so the check reads its __inputTokens; a built module is compiler output already checked when it was built. Adaptation rules are read from their authored values (adaptations or the normalized __adaptations) before the resolved __adaptationRules. A theme with tempo or a duration scale now builds; a private var an author writes is still reported.
… other rows keep main's text The row was wider than the table's evidence and failure columns, so the formatter realigned all thirteen rows. It now fits them, with the same evidence and failure signal in fewer words, and the other rows are main's again.
…beside its reader Moved here from meta#8661, which no longer writes them since nothing there reads them: a theme that sets tempo or character writes data-astryx-motion-tempo and data-astryx-motion-character on its wrapper and, for the root theme, on <html>, where the animator's themeKnobs.ts reads them without importing the theme (spec:AST-108 FR3). Without them, themeKnobs.test.tsx fails on the merge before this commit. Their three tests move with them; the comment beside the reduced-motion mark keeps the record's known-gap wording.
…them and the animator follows 'always' meta#8661's docs say what it ships: the knobs drive the CSS tokens, no spring reads them yet, and the animator doesn't follow reducedMotion: 'always' yet. This pull request ships both, so its docs say so: - The motion topic, the theme guide's row, the theme template, the motion field's JSDoc and themeMotion.ts say tempo and character reach the touch springs, and that the animator follows 'always'. - The theme authoring contract's known gaps: resolveThemeMotion's role tables stay unshipped for server code, while the animator's springs follow the knobs through the theme root's attributes; the animator leaves the list of what doesn't follow the mark yet. - The theme-motion-knobs changeset says the same, so the release notes describe the springs once.
…the --ease-standard beneath it (spec:AST-108 FR2) At 'standard', --ease-entrance and --ease-move equal --ease-standard. Fails now when that --ease-standard is an explicit token from beneath the layer: a child of a base with one, or an adaptation rule of a theme with one, that sets character 'standard' gets Astryx's default curve.
…olved --ease-standard A layer that sets character 'standard' wrote Astryx's default curve, or its duration scale's easing, to --ease-entrance and --ease-move, though FR2 makes them equal --ease-standard, which can be an explicit token from beneath the layer: its base's, or the theme's for an adaptation rule. resolveThemeValues now writes them from the resolved --ease-standard: the layer's own, else the seed's, else, for a rule (resolved without a seed), its theme's, passed as standardBeneath. The case of an explicit --ease-standard in the layer itself moves into the same rule.
…of the knobs' types, defaults and clamping
…ay it in fewer lines The press-state helper and the built-sheet check read a release clock's base duration at tempo 1 with one pattern for its scaled and capped forms, instead of one regular expression per form; pressFeedback.test.ts still pins both forms in the source. The comments on the clock, its timer and the mark's custom property keep what they say in fewer lines, to keep this pull request under the scope limit.
…prings, beside the tempo (spec:AST-108 FR3) The springs are to read the knobs from the same CSS the durations use, so the knobs inherit into nested themes and follow adaptation rules as the durations do. A theme or rule with a tempo already writes it as a private custom property; one that sets a character must write that too. Fails now: no character is written.
…tion-character Beside --_astryx-motion-tempo, which a theme or rule with a tempo already writes for the press release fade, the character is now a private custom property too, so the springs can read both knobs from the CSS the durations use (spec:AST-108 FR3). A theme that sets neither writes neither, so existing themes generate exactly what they did (FR5).
…ations use (spec:AST-108 FR3, FR4a) A nested theme that sets no knob inherits its enclosing theme's duration and easing tokens, and an adaptation rule's knobs apply where its condition holds, but the springs read only attributes Theme wrote on its own wrapper. The springs are to read the same custom properties (--_astryx-motion-tempo, --_astryx-motion-character), so they inherit and follow rules as the durations do. - jsdom: the knobs are the custom properties declared on the element; the role tests' element carries the ones its theme writes. - Chromium and WebKit (themeKnobs.interaction.browser.spec.ts, themes from defineTheme and generateThemeCSS): a nested theme that sets no knob plays its enclosing theme's, one that sets character 'standard' stops it, a coarse-pointer rule's knobs apply on a phone and not under a mouse, and a spring in a nested theme plays at the enclosing tempo and starts on its first frame while the layout is dirty. In Chromium, Long Animation Frames show that reading the knobs forces no layout, where reading a size does. The nested, rule and spring cases fail now.
…animator's end hold An animation's duration includes the 5 s it holds its end state (END_HOLD_MS), and an expressive character lengthens a spring's settle on its own, so the check compares, less the hold, the theme that sets only character 'standard' inside the outer tempo of 1.4 with a spring outside every theme. The layout is dirtied across a 12,000-row tree.
…om properties, so nested themes and adaptation rules reach them themeMotionKnobs read attributes Theme wrote on its own wrapper, so the springs played Astryx's own knobs inside a nested theme that sets none, whose durations inherit the enclosing theme's, and ignored an adaptation rule's tempo and character, which its durations follow (spec:AST-108 FR3, FR4a). It now reads --_astryx-motion-tempo and --_astryx-motion-character in one getComputedStyle at each motion start, custom properties only, which forces no layout. Theme no longer writes the tempo and character attributes, and their tests go with them; the reduced-motion mark stays. resolveMotion's test now checks that no role's own variable is read, only the knobs, and animatedLayout's that an area inherits the tempo its theme writes.
…cter, and the springs read the knobs' custom properties The motion docs said exits and fades never overshoot, but a role whose character weight is zero keeps its measured bounce, and springs.menuClose overshoots 0.6%. They now say exits and fades keep their measured bounce (motion topic, theme template, defineTheme and motionKnobs JSDoc, resolveMotion). The record's note on role tables says the springs follow the custom properties a theme or adaptation rule writes, no longer attributes.
…ts timing is a test annotation @typescript-eslint/promise-function-async asks a function that returns a promise to be async: knobsOf and the two page.evaluate callbacks. The measured forced style and layout is reported as a test annotation instead of a console line (no-console).
…er code reads resolveThemeMotion The theme authoring contract said the animator reads resolveThemeMotion; it reads the knobs from the custom properties the theme writes (motion/themeKnobs.ts), and resolveThemeMotion is server code's pure read. The motion README's themeKnobs row and resolveMotion's header still described the attribute design; they now say the knobs are inherited custom properties, read in one style read that forces no layout.
…xts describe the custom-property knobs The motion guide said 'crisp' decelerates without overshoot, which is its entrance and move curves; a crisp spring keeps part of its bounce (springs.emphasis keeps 0.2). The guide and motionKnobs' JSDoc now say crisp gives the curves no overshoot and takes some bounce out of springs, and expressive adds some. themeMotion.ts's header, animator.ts's role note, the motion README and themeKnobs.test.tsx's header still described the attribute design or a role that never follows the theme; they now say a role plays its table value moved by the knobs, read from the custom properties the theme writes, and that no role's own variable retunes it (DEC-11, DEC-15). The README lists the new browser check.
…ry reader of the mark, and themeKnobs' SYNC names its test file resolveThemeMotion's comment still said the animator reads it; the animator reads the knobs' custom properties (themeKnobs.ts). Theme's FR6 comment listed only adaptation rules and the press release fade as the mark's readers; the animator, the layout glide and reducedMotionFade() read it too. themeKnobs.ts's SYNC list named themeKnobs.test.ts for themeKnobs.test.tsx.
…or's layout glide and Reduce Motion fade The record's known-gap paragraph, the motion topic, the changeset, the theme template, defineTheme's JSDoc and themeMotion.ts listed motion-keyed rules, the press release fade and the animator as what follows 'always' so far; the layout glide (motion/animatedLayout.ts) and reducedMotionFade() honour the mark too, and they now say so.
There was a problem hiding this comment.
Approval waits on an engineering owner's decision on this head's change to architecture:theme-authoring-contract: it replaces the accepted statement that resolveThemeMotion returns role tables for the animator and for server code with one where the animator reads the theme's custom properties and the resolver serves server code only. Any engineering owner, the author included since they are one, can record that decision by commenting /approve-spec <full head SHA> on this pull request, or by landing the amended record on main. Whatever the decision, these need changes:
- Land the stacked pull requests and rebase, so the diff carries only
resolveSettleMsand its tests (it carries 16,980 authored lines today). - Make
reducedMotion: 'always'reach the remaining component styles and script readers, asspec:AST-108FR6 requires, or hold the option back until it does. - Make
resolveSettleMsfollow Reduce Motion and end with the spring the animator plays (FR4, FR4a), and make the FR4a glide test set--_astryx-motion-tempoand--_astryx-motion-characterinstead of attributes nothing reads.
[Automated review]
| it('lasts what a CSS part timed from springs.settle lasts in the same theme, so the two land together (spec:AST-108 FR4a)', async () => { | ||
| await changeOnce( | ||
| '', | ||
| 'data-astryx-theme="lively" data-astryx-motion-tempo="1.2" data-astryx-motion-character="expressive"', |
There was a problem hiding this comment.
Nothing at this head reads data-astryx-motion-tempo or data-astryx-motion-character: themeMotionKnobs reads the --_astryx-motion-tempo and --_astryx-motion-character custom properties. So this case runs at Astryx's own knobs and would still pass if the glide ignored the theme. Passing '--_astryx-motion-tempo: 1.2; --_astryx-motion-character: expressive' as the style argument, as the tempo case above does, would make it test what its name says.
| // The loop reads the duration before a theme's tempo scales it | ||
| // (spec:AST-108 FR4). | ||
| default: `var(--_astryx-pre-tempo-duration-slow-min, ${durationVars['--duration-slow-min']})`, | ||
| '@media (prefers-reduced-motion: reduce)': '3s', |
There was a problem hiding this comment.
Under a theme's reducedMotion: 'always' with the person's own setting off, this rule never applies, so the loop keeps its full speed; spec:AST-108 FR6 asks every style keyed on prefers-reduced-motion to key on the theme's mark too. Skeleton's pulse and the other component styles and script readers the record now lists as a known gap behave the same way.
|
|
||
| - An internal, pure `resolveThemeMotion(theme)` returns a theme's motion | ||
| knobs, durations, and easings for server code; it touches no DOM and has no | ||
| import-time side effects (`spec:AST-108` FR6). The animator reads the knobs |
There was a problem hiding this comment.
This replaces the record's accepted statement that resolveThemeMotion returns role tables "for the animator and for server code" with a new reading: the animator reads the theme's custom properties and the resolver serves server code. An engineering owner needs to approve that, for example by commenting /approve-spec <full head SHA>.
Depends on #8677. Review only this PR's change: vjeux/astryx@5e235e6...a31f1ec; the merges after take #8677's head and main.
Why
A CSS part that plays alongside a spring, such as the scrim fading under a sheet or a menu's content under its grow, has to start and land with the spring. Timed on its own clock, it drifts as soon as a theme's
tempoorcharacterchanges the spring.What led to it
This comes from the work to make Astryx's touch components behave as native iOS ones do (sheets, menus, gestures and their motion).
spec:AST-108FR4a asks paired animations to end within a frame of each other at every knob value. #8677 made the springs follow the knobs, so the CSS side now needs the same number.Options and tradeoffs
Why this choice
The helper keeps CSS parts in CSS and gives them the exact time the spring will take. It measures the settle on the spring's progress, as the layout glide does, because the animator's pixel and degree thresholds fit a moving element, not a fade.
What changes
resolveSettleMs(element, spring)returns the spring's settle time in milliseconds at the knobs the element inherits from its theme. A fixed role (finger tracking) or a spring that is not a role gives its own.How to check it
pnpm exec vitest run packages/core/src/motion/themeKnobs.test.tsx -t FR4a.tempo: 1.5it is 1.5 times as long; withcharacter: 'expressive'it is the resolved spring's own.resolveSettleMs(area, springs.settle).Evidence
resolveSettleMs.Scope