Repository navigation
Conversation
…fine pointer); the changeset says the same animator.test.ts keeps this layer's header (no compositor browser spec in this layer). The changeset now says decay sends a spring back from past an edge through the Reduce Motion policy: a cross-fade on a coarse pointer, at once elsewhere.
…side a scroll view The press profiles' README said a cell's highlight always waits the content-touch delay. PressTracker waits it inside a scroll view and lights at once outside one (spec:AST-094 FR11), as its own header says; the README now says so too.
…angeset wording) as a dependency
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.
…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.
…he chat scroll, streaming text and swipe settle read it (spec:AST-108 FR6)
A theme's reducedMotion: 'always' marks its root, but a style keyed with stylex.when.ancestor('[data-astryx-reduced-motion]') uses the default marker, a class neither the Theme wrapper nor <html> carries, so it never matched; the default marker itself would let every default-marker :hover rule under a theme match. reducedMotionScope is a marker of its own: the marked wrapper carries it, in server markup too, and the root provider writes it on <html> beside the mark, so a portaled layer matches. A Reduce Motion arm's twin nests the arm's own pointer query, which compiles to the media arms' place in the cascade: above defaults and pseudo-classes, below hover-guarded states. reducesMotion(element) reads an element's ancestry, as the twins do, and useReducesMotion() reads the setting live or the theme tree, for a hook without an element; useChatStreamScroll, useStreamingText and the swipe settle use them. Two guards in reducedMotion.test.ts hold the rest: every arm needs its twin and every script read of the setting needs the mark, each with a pending list that only shrinks.
…s lines (spec:AST-108 FR6)
A component's twin key, stylex.when.ancestor('[data-astryx-reduced-motion]', reducedMotionScope), runs past the line length wherever it sits deep in a style, and Prettier then breaks it across lines with a trailing comma. The guard matched it as one literal string, so every such twin read as missing. It matches the key whitespace-insensitively now.
…which StyleX's types need (spec:AST-108 FR6) StyleX's types flatten a nested condition object into a property's value union only when it has a default key. A twin without one keeps its nested object in the union, so a style holding it is no longer assignable to a typed xstyle (a layer's animations, passed to Popover and its kin, failed the typecheck so). default: null emits no rule; the compiled CSS is byte-identical.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
There was a problem hiding this comment.
Requesting changes: the reduced-motion marker and the three converted readers match spec:AST-108 FR6, but this diff also carries the unmerged stack beneath it (#8446, #8076, #8661, #8677), about 17,500 authored lines against a 2,000-line size limit, so please land those first and let this pull request shrink to its own ~870 lines.
- The changeset says
reducedMotion: 'always'reaches Markdown while it streams, but Markdown's entry fade (streamingStyles.fadeIninMarkdown.tsx) still plays under'always', so add its twin or narrow the changeset. - The script-reader guard in
motion/reducedMotion.test.tsonly matches a query written inline, souseMediaQuery(REDUCED_MOTION_COARSE_QUERY)inBottomSheetPanel.tsxwill slip past it once that file's other read converts. - The Vercel preview deployment fails on this head while it passed on the parent commit, which is worth checking before merge.
[Automated review]
| '@astryxdesign/core': patch | ||
| --- | ||
|
|
||
| [fix] A theme's `reducedMotion: 'always'` reaches streaming text (`useStreamingText`, and so Markdown while it streams), the chat scroll's follow and a swipe row's settle, as the person's Reduce Motion setting does. The mark also carries a marker class, on the theme's root and, for the root theme, on `<html>`, so components' Reduce Motion styles can key on it, portaled layers included. |
There was a problem hiding this comment.
This says 'always' reaches "Markdown while it streams", but Markdown still wraps each arriving chunk in streamingStyles.fadeIn (packages/core/src/Markdown/Markdown.tsx:637-652), whose fade drops to 0s only under @media (prefers-reduced-motion: reduce), and Markdown/Markdown.tsx is still on PENDING_TWINS. Under an 'always' theme with the setting off, a streamed chunk still fades in (opacity 0.407 about 40 ms after it arrives, transition-duration: 0.3s). Either add the twin to fadeIn.transitionDuration and drop Markdown from PENDING_TWINS, or narrow this line to Markdown's progressive reveal.
| const RAW_SETTING_READ = | ||
| /(matchMedia|useMediaQuery)\(\s*'\(prefers-reduced-motion: reduce\)'/; |
There was a problem hiding this comment.
This matches only a query written inline. BottomSheet/BottomSheetPanel.tsx:665 reads useMediaQuery(REDUCED_MOTION_COARSE_QUERY) ('(prefers-reduced-motion: reduce) and (pointer: coarse)', :129), so once :554 converts, this guard calls the file converted and forces it off PENDING_SETTING_READERS while :665 still ignores the mark. Resolving string constants the way armsWithoutTwins does for arms, or flagging any '(prefers-reduced-motion literal outside a style key, would close it.
…wn, and the script guard catches a read through a constant (spec:AST-108 FR6) Markdown's own CSS entry fade still plays under a theme's reducedMotion: 'always' until its twin lands, so the changeset names useStreamingText's progressive reveal, which this pull request converts, and no longer says Markdown while it streams. The script guard matched only a media query written inline, so a read through a constant (BottomSheetPanel's useMediaQuery(REDUCED_MOTION_COARSE_QUERY)) passed it; it now follows a constant the file holds the query in. Red first: a probe reading the setting through a constant passed the guard before and fails it now.
There was a problem hiding this comment.
Requesting changes: the reduced-motion marker, the three converted readers and the widened script guard match spec:AST-108 FR6, and the changeset now describes exactly what ships. Against main, though, this diff still carries the unmerged stack beneath it (#8446, #8076, #8661, #8677), about 17,500 authored lines against the 2,000-line limit, so please land those first and let this pull request shrink to its own ~880 lines.
[Automated review]
|
Thanks.
|
Review only this PR's change: vjeux/astryx@f42fdcd...403091e
Why
A theme's
reducedMotion: 'always'marks its root, but most of core keys Reduce Motion onprefers-reduced-motionalone: the mark reaches only the press release fade, the adaptation rules and the animator.spec:AST-108FR6 asks every Reduce Motion style and script reader to key on it too. This lays the foundation and converts three script readers; stacked pull requests convert the rest by family.What led to it
With StyleX 0.19,
stylex.when.ancestor('[data-astryx-reduced-motion]')compiles to:where(.x-default-marker[data-astryx-reduced-motion] *), a class neither the Theme wrapper nor<html>carries, so it never matches. A bare marker key also lands at priority 3040, below pseudo-class states (3130) and the media arms (3200).Options and tradeoffs
:hoverrule under it would match.--_astryx-reduced-motionalone: only computed values such as durations, neveranimation-name.Why this choice
reducedMotionScopegoes on the marked wrapper, server markup included, and on<html>from the root provider's layout effect, so portaled layers match. A twin,[stylex.when.ancestor('[data-astryx-reduced-motion]', reducedMotionScope)], nests the arm's pointer query ('@media all'without the split;motion/reducedMotion.tsshows one) and compiles where the media arms sit, at priority 3240: above defaults and pseudo-classes, below hover-guarded states, keepingdesign:reduced-motion's split.reducesMotion(element)reads an element's ancestry, as the twins do;useReducesMotion(), for a hook without an element, reads the setting live or the theme tree, where a nested'system'theme cannot remove an ancestor's mark. The chat scroll, streaming text and swipe settle use them. Readers tied to a component's CSS (AppShell, MobileNav, BottomSheet, Carousel) convert with its family's styles, so no component is half-reduced.Two guards hold the rest, each with a pending list that only shrinks: every Reduce Motion arm needs its twin (40 files, 88 arms pending), and every script read of the setting needs the mark (6 pending).
How to check it
reducedMotionScope.test.tsx: the marker in server markup and on<html>, and a mark-keyed style in the tree and in a layer portaled to<body>.'always'theme, the setting off.motion/reducedMotion.test.ts: the guards.Evidence
astryx.cssis byte-identical (34,293 B gzipped): a marker adds no rule.<html>(architecture:theme-applicationINV2), and layers render on the client.