Skip to content

feat(core): the animator's springs follow the theme's tempo - #8677

Merged
cixzhang merged 8 commits into
meta:mainfrom
vjeux:uikit/f1-theme-motion-springs
Nov 5, 2026
Merged

cixzhang merged 8 commits into
meta:mainfrom
vjeux:uikit/f1-theme-motion-springs

Conversation

@vjeux

@vjeux vjeux commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

The theme's character stays out of this pull request: its spring tables wait on #9704. This one is tempo only.

User need

A theme author sets motion.tempo to slow down or speed up their product's motion. Today only the CSS transitions follow it. The touch springs keep Astryx's own timing: a sheet's settle, a switch's travel, a slider's release and the animated-layout glide. So with a tempo of 1.5, a screen's fades take 1.5 times as long while its sheet still settles at tempo 1.

Problem and solution fit

Every touch spring and role duration goes through resolveMotion(element, role), and that function passed each role through unchanged. It now reads the tempo of the theme element is in and stretches the role:

  • A spring's undamped period grows by the tempo and its damping ratio stays the same. So it keeps its shape and takes tempo times as long.
  • A duration role is multiplied by the tempo.
  • The layout glide reads springs.settle through it, at the area.

Current specification

  • spec:AST-108 FR3 (current): "a spring's duration scales by tempo".
  • FR4 lists what stays fixed.
  • FR5: a duration scale alone leaves the springs at tempo 1.
  • spec:AST-094 FR45 (current): "The theme's tempo scales the spring's duration".
  • FR3's character offsets aren't here. The spec owner keeps them parked until spec(AST-108): the fluid character's motion tables #9704, which redefines them, lands.

Public delta and caller burden

  • There's no API change. A theme with motion: {tempo: 1.5} now also plays every touch spring and role duration inside it 1.5 times as long, the glide included.
  • Nothing changes for a theme that sets no tempo, or only a duration scale. The theme writes --_astryx-motion-tempo only when it sets tempo, and the reader treats a missing value as 1.
  • These stay as measured: finger tracking (springs.fingerTracking and Switch's drag tracking), ProgressBar's reduced-motion pulse, curves, and anything that isn't a role (momentum, the rubber band, paging, press clocks and thresholds).
  • Switch's release settles on a curve it builds itself. It now resolves its lift and settle springs and its minimum lift at the thumb, as its lift does, so the release takes the theme's tempo too.
  • A theme layer whose durations come from a duration scale alone now writes a tempo of 1, so it keeps its springs at the pace of its own durations inside a theme with a tempo (FR5). A child of a theme with a tempo keeps the base's tempo; a layer that sets only character or a few explicit durations writes none.
  • The docs that said the springs don't read the knobs now say they follow tempo but not character yet: the motion doc, defineTheme's motion JSDoc, the theme template and the author-a-theme doc.

Design and alternatives

  • Where the tempo comes from: the reader uses the private property the theme already writes beside its duration tokens. That's one custom-property read per resolve, which forces no layout. It inherits through nested themes and follows adaptation rules, as the durations do. Putting attributes on Theme's wrapper was rejected because they don't inherit and can't take rule conditions.
  • How a spring stretches: each spring stretches in its own form (response, duration, or stiffness and damping) instead of converting through one form. So tempo 1 returns the table value itself, and nothing is rounded.
  • No double scaling: a resolved spring is cached per role and tempo. It isn't a role itself, so when the animator resolves it a second time, it's left alone.
  • Every pointer: the glide follows tempo with a mouse too, as spec:AST-105 DEC-2 decides (cixzhang, 2026-11-04): "a theme that sets character or tempo sees its glides change with the rest of its motion, under every pointer." The owner's decision for the interim spring (2026-11-04, 16:24 UTC): "Until the glide moves to the curve tokens (AST-105 DEC-2), its interim springs.settle follows tempo under every pointer." At tempo 1 nothing changes on desktop. AST-108 FR1 has tempo scale every motion duration, and FR3 scales every AST-094 FR4 role's spring. The glide plays the FR4 role springs.settle under every pointer (AST-105 FR4, FR5), and AST-107 FR3 says animate's spring follows the theme's tempo. The CSS tokens already follow tempo with a mouse, so the glide now keeps step with them.
  • One tempo reader: the press release fade plays --duration-exit, which the tempo already scales, so it needs no reader of its own.

Evidence

  • themeTempo.interaction.browser.spec.ts, Chromium and WebKit on the phone profile:
    • A spring the animator plays inside a tempo 1.5 scope, inherited through a nested element, draws at 1.5 × t what it draws at t outside the scope, within 1.5 px over a 300 px travel.
    • Finger tracking draws the same in both.
    • Under three real themes that set only motion.tempo, the glide lasts 0.75, 1 and 1.5 times as long.
  • resolveMotion.tempo.test.ts:
    • Each spring form's settle time scales exactly, and its curve matches at the scaled times.
    • A duration role scales.
    • Curves, finger tracking and the busy loop don't.
    • Tempo 1 and no tempo return the table value itself.
    • The animator plays the slowed spring.
  • themeTempo.test.ts reads the theme's value, and falls back to 1 when there is none or it isn't a positive number.
  • animatedLayout.test.ts checks the glide at the area's tempo.
  • useSwitchTouch.test.ts: a release right after a press waits the minimum lift and settles, all exactly 1.5 and 0.75 times as long at those tempos.
  • themeMotion.test.ts: a theme and an adaptation rule with a duration scale alone write a tempo of 1; a child of a tempo theme keeps its base's tempo.
  • The browser spec also runs the glide with a mouse (a desktop context, no coarse pointer): 0.75, 1 and 1.5 times as long. A duration-scale theme nested in a tempo 1.5 theme glides at its own pace (1×) beside the outer theme's 1.5×.
  • The specs that set a tempo on the document (ProgressBar's move and pulse) now set it on the component: the story's theme root declares its own tempo.
  • Red first for the Switch and duration-scale fixes: with only those two files reverted (35f25756c9), the Switch case reads a ratio of 1.0 and both theme cases find no tempo.
  • Red first, on main with only these tests added (a5b1282f64): 6 unit cases fail and themeTempo.test.ts has no module to load. The browser scope case is 25 px off; finger tracking passes on both, as it should.

Scope

  • One capability is added.
  • Unrelated fixes, cleanup, and policy changes were removed or split.
  • Consumer docs, focused tests, and migration evidence ship with the feature.
  • Public text and artifacts contain no internal Meta context.

Testing

Gate at 50e47ea46d (rebased onto main after the shared press release fade moved to --duration-exit), side by side with main on one box:

  • Build, Storybook build, typecheck and lint pass; changeset coverage passes.
  • pnpm test:ui 20,636 vs 20,622, 0 failed; pnpm test:node 16,561 passed.
  • Interaction Chromium 668 vs 663, 0 failed.
  • Interaction WebKit 568 vs 564. Two cases fail, neither from this change:
    • The harness's shader-cache case fails on both arms; that box's container had no seeded shader cache.
    • ListRow's "the release keeps fading while the main thread is blocked (video)" also fails on main: in a same-moment pair of five runs each, main failed 5 and this change failed 4.

Earlier, at 6755a6a5a8: the tempo spec three times, Chromium 15/15 and WebKit 15/15; ProgressBar's move and pulse specs 26/26.

Feel check: open the Core/Theme motion tempo story ("Three tempos") on a phone. In each row, toggle the switch, drag the slider, slide the segments, and open "Shipping details". The 1.5 row should feel half again as slow as the middle row, and the 0.75 row quicker. Each finger drag still tracks the finger.

@vercel

vercel Bot commented Oct 27, 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 5, 2026 4:33pm UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 5, 2026, 5:00 PM UTC

@github-actions github-actions Bot added needs:design-review Affects visuals — Design should review needs:spec-owner-review Current knowledge records await owner approval labels Oct 27, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 27, 2026

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

Approval of this pull request waits on a spec decision. On main, spec:AST-094 FR4 and DEC-11 keep the touch motion roles internal, while the spec:AST-108 requirements (with the spec:AST-094 DEC-15 and related amendments) that let a theme's tempo and character retune them appear here only as proposed text, in an earlier version than the one approved on #8613. An engineering owner records that decision by commenting /approve-spec 5e235e66b2a97e2e81d798820889f71d03bd8d29 on this pull request (the author may do so if they are an engineering owner); otherwise the pull request waits for #8613 to land on main and rebases onto it. Whatever the decision, two changes are needed:

  • Rebase onto main once #8076 and #8661 land, so the diff carries only this change; today it counts 17,872 authored lines against a 499-line own change.
  • Make the springs follow the knobs the CSS follows (an adaptation rule's tempo or character, and an enclosing theme's knobs, which themeMotionKnobs never reads), or state in the spec that they do not.

[Automated review]

Comment thread packages/core/src/motion/themeKnobs.ts Outdated
Comment on lines +58 to +78
export function themeMotionKnobs(
element: Element | null | undefined,
): ThemeMotionKnobsAt {
const scope = element?.closest?.(THEME_SCOPE);
if (scope == null) {
return DEFAULT_KNOBS;
}
const tempoValue = Number(scope.getAttribute('data-astryx-motion-tempo'));
const tempoIsSet =
scope.hasAttribute('data-astryx-motion-tempo') &&
Number.isFinite(tempoValue) &&
tempoValue > 0;
const characterValue = scope.getAttribute('data-astryx-motion-character');
const character: ThemeMotionCharacter =
characterValue === 'crisp' || characterValue === 'expressive'
? characterValue
: 'standard';
if (!tempoIsSet && character === 'standard') {
return DEFAULT_KNOBS;
}
return {tempo: tempoIsSet ? tempoValue : 1, tempoIsSet, character};

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.

The springs read only this scope's attributes, which Theme.tsx writes from the theme's root motion. The CSS also follows an adaptation rule's tempo or character, and inherits an enclosing theme's scaled --duration-* when the nearest theme sets no motion. In both cases the springs and the CSS diverge. For example, a when: {pointer: 'coarse'} rule with tempo: 1.3 makes the CSS 1.3× while the springs keep the root tempo. Either resolve the knobs the way the CSS does, or state the limit in AST-108 FR3 and FR5, with a test either way.

Comment thread packages/core/src/motion/animator.ts Outdated
return null;
}
const curve = solveSpring(toSpring(spring), {from, to, velocity});
// The motion role it plays: its table value while DEC-11 holds.

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.

After this change a role follows the theme's knobs (resolveMotion.ts), so "its table value while DEC-11 holds" no longer describes it. The same goes for line 137 and for README.md lines 49-50 and 72-73.

@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · e4a2b95

Storybook · Sandbox · built from this PR's head

Modified components

Component Size LoC Visual · A11y · RTL
Slider 92.9 kB (+416 B) · 8.3 kB CSS (0) — — · — · —
TreeList 41.2 kB (+412 B) · 5.4 kB CSS (0) — — · — · —
ProgressBar · Storybook 11.4 kB (+399 B) · 6.2 kB CSS (0) 568 (0) — · — · —
33 more components
Component Size LoC Visual · A11y · RTL
SideNav 76.7 kB (+394 B) · 10.1 kB CSS (0) — — · — · —
AppShell 76.7 kB (+392 B) · 12.2 kB CSS (0) — — · — · —
DateTimeInput 187.0 kB (+391 B) · 17.2 kB CSS (0) — — · — · —
Switch · Storybook 90.5 kB (+386 B) · 9.5 kB CSS (0) 1,723 (+6) — · — · —
TimeInput 141.2 kB (+386 B) · 14.5 kB CSS (0) — — · — · —
DateInput 164.3 kB (+370 B) · 15.5 kB CSS (0) — — · — · —
SegmentedControl 84.8 kB (+362 B) · 6.7 kB CSS (0) — — · — · —
PowerSearch 260.7 kB (+360 B) · 22.6 kB CSS (0) — — · — · —
charts/Chart 169.2 kB (+346 B) · 494 B CSS (0) — — · — · —
Collapsible 25.0 kB (+339 B) · 4.5 kB CSS (0) — — · — · —
BottomSheet 56.9 kB (+308 B) · 7.5 kB CSS (0) — — · — · —
lab/ListInput 85.6 kB (+79 B) · 1.6 kB CSS (0) — — · — · —
MultiSelector 80.4 kB (+77 B) · 15.6 kB CSS (0) — — · — · —
TableCell 84.3 kB (+73 B) · 16.7 kB CSS (0) — — · — · —
lab/ChatReactionBar 69.7 kB (+70 B) · 1.1 kB CSS (0) — — · — · —
Pagination 93.3 kB (+69 B) · 16.6 kB CSS (0) — — · — · —
lab/ChatEmojiPicker 69.2 kB (+69 B) · 773 B CSS (0) — — · — · —
lab/Comments 273.9 kB (+69 B) · 2.5 kB CSS (0) — — · — · —
TableHeaderCell 83.9 kB (+67 B) · 16.5 kB CSS (0) — — · — · —
Selector 83.3 kB (+65 B) · 16.2 kB CSS (0) — — · — · —
lab/TourStep 61.4 kB (+64 B) · 590 B CSS (0) — — · — · —
richtext/RichTextEditorToolbar 234.1 kB (+63 B) · 454 B CSS (0) — — · — · —
Table 92.5 kB (+61 B) · 16.9 kB CSS (0) — — · — · —
DropdownMenu 78.0 kB (+60 B) · 16.4 kB CSS (0) — — · — · —
Markdown 153.8 kB (+60 B) · 19.2 kB CSS (0) — — · — · —
ContextMenu 83.1 kB (+59 B) · 16.3 kB CSS (0) — — · — · —
MoreMenu 78.2 kB (+57 B) · 16.4 kB CSS (0) — — · — · —
lab/CommentThread 268.4 kB (+57 B) · 2.4 kB CSS (0) — — · — · —
Popover 55.4 kB (+55 B) · 12.8 kB CSS (0) — — · — · —
lab/CommentComposer 109.1 kB (+49 B) · 1.9 kB CSS (0) — — · — · —
Tokenizer 74.9 kB (+41 B) · 17.1 kB CSS (0) — — · — · —
Overlay 21.6 kB (0) · 3.9 kB CSS (0) — — · — · —
Toast 60.7 kB (0) · 9.0 kB CSS (0) — — · — · —

Package size:

  • @astryxdesign/cli 3.37 MB packed (+12 B); no new dependencies
  • @astryxdesign/core 6.58 MB packed (+3.0 kB); no new dependencies
  • @astryxdesign/theme-butter 45.6 kB packed (+18 B); no new dependencies
6 more packages
  • @astryxdesign/theme-chocolate 13.6 kB packed (+16 B); no new dependencies
  • @astryxdesign/theme-gothic 32.6 kB packed (+17 B); no new dependencies
  • @astryxdesign/theme-matcha 28.0 kB packed (+19 B); no new dependencies
  • @astryxdesign/theme-neutral 35.5 kB packed (+18 B); no new dependencies
  • @astryxdesign/theme-stone 37.6 kB packed (+14 B); no new dependencies
  • @astryxdesign/theme-y2k 34.7 kB packed (+14 B); no new dependencies

API changes: none in this pull request

Committed API report behind this build: cli 9 rows, core 8 rows; run pnpm api:report.

Wide change (shared styles: packages/core/src/theme/defineTheme.ts, packages/core/src/theme/resolveThemeValues.ts, packages/core/src/theme/themeMotion.test.ts, and 1 more): add ci:run-visual, ci:run-a11y or ci:run-rtl to run them.


Generated by PR Enrichment workflow | View full report

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026

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

Approval of this pull request waits on a spec decision: on main, spec:AST-094 FR4 and DEC-11 keep the touch motion roles internal, and the spec:AST-108 records (with architecture:theme-authoring-contract INV15) that let a theme's tempo, character and reducedMotion change them are proposed here without approval for this head. An engineering owner records that decision by commenting /approve-spec f2546dbdc1b0830d730cdae6793c51a5d6c3028f on this pull request (the author may do so as an engineering owner), or the records land on main first. Whatever the decision, these changes are needed:

  • Make reducedMotion: 'always' reach the components' own reduced-motion styles and script readers before the docs and changeset promise that it reduces motion for everyone, or say what it reaches today.
  • Write --pre-tempo-duration-* wherever a theme writes a duration, so a nested theme's scale or explicit duration still times Spinner and Skeleton under a theme that sets tempo.
  • Make the springs follow the knobs the CSS follows (an adaptation rule's tempo or character, and an enclosing theme's knobs), or state that limit in the spec, with a test either way.
  • Rebase once #8076 and #8661 land, so the diff carries only this change (17,911 authored lines today).

[Automated review]

Comment thread packages/cli/assets/docs/motion.doc.mjs Outdated
items: [
"`tempo` scales every motion duration: 1 is Astryx's own, 0.75 the quickest and 1.5 the slowest (values outside are clamped).",
"`character` sets how lively motion is: `'crisp'` decelerates without overshoot, `'standard'` is Astryx's own, and `'expressive'` overshoots slightly on entrances and moves. Exits and fades never overshoot.",
"`reducedMotion: 'always'` reduces motion for everyone, as if each person had turned on Reduce Motion; `'system'`, the default, follows each person's setting. No value loosens Reduce Motion.",

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.

No core component reads the data-astryx-reduced-motion mark yet: the 50 non-test core files that read prefers-reduced-motion in a style or script (Dialog, BottomSheetPanel, TopNavMegaMenu, Step among them) ignore it, so a person without the setting still gets full motion under a theme on 'always'. Only the theme's own when: {motion} adaptation rules and the internal animator honour it. Either make those styles and script readers key on the mark (as spec:AST-108 FR6 requires) before promising this, or say what 'always' reaches today. The changeset's "so motion is reduced for everyone" has the same problem.

): Record<string, string> {
const knobs = resolveMotionKnobs(config);
const tokens: Record<string, string> = {};
if (knobs.tempoIsSet) {

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.

--pre-tempo-duration-* is written only when this theme sets tempo, and resolveThemeValues copies an explicit duration into it only when the name already exists. Custom properties inherit, so a nested theme with its own duration scale or explicit duration but no tempo leaves Spinner and Skeleton reading the outer theme's pre-tempo value. Example: inside a tempo: 1.5 theme, a nested motion: {fast: 100, medium: 250, slow: 600, ratio: 0.75} theme's Spinner turns every 730 ms instead of its own 450 ms --duration-slow-min. Writing the pre-tempo twin wherever a theme writes a duration would fix it; a nested-theme test would pin it.

* the element is in (`spec:AST-108` FR3): `tempo` scales a spring's duration
* and a role's duration, and `character` moves a spring's bounce by its
* role's weight (1 for entrances and presentations, 0.5 for state changes, 0
* for exits, which never overshoot). The theme's `MotionScaleConfig` alone

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.

"0 for exits, which never overshoot" no longer matches spec:AST-108 FR3 at this head, which says a weight-0 role keeps its measured bounce: springs.menuClose (damping ratio 0.85) overshoots by about 0.6% at every character. The same wording sits in theme/motionKnobs.ts:34, theme/defineTheme.ts:243, cli/assets/theme.template.ts:186 and motion.doc.mjs:159.

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026

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

Approval of this pull request waits on one spec decision: architecture:theme-authoring-contract INV15 adds "an explicit token anywhere in the lineage still wins", which no record on main states and which lets a base theme's explicit --duration-* or --ease-* token beat a child's own duration scale. An engineering owner records that decision by commenting /approve-spec eeec68709d2d3c5de4c5138180bcce7b740a4f99 on this pull request (the author may do so as an engineering owner), or the clause can be dropped so a child's own scale keeps winning. Whatever the decision, these changes are needed:

  • Make component styles and script readers honour the reducedMotion: 'always' mark, as spec:AST-108 FR6 requires, or keep 'always' out of the docs and changeset until they do.
  • Make the springs follow the knobs the CSS follows: an adaptation rule's tempo or character, and an enclosing theme's knobs.
  • Write --pre-tempo-duration-* wherever a theme writes a duration, so a nested theme still times Spinner and Skeleton under a theme that sets tempo.
  • Rebase once #8076 and #8661 land, so the diff carries only this change (17,803 authored lines today).

[Automated review]

Comment thread packages/core/src/motion/themeKnobs.ts Outdated
export function themeMotionKnobs(
element: Element | null | undefined,
): ThemeMotionKnobsAt {
const scope = element?.closest?.(THEME_SCOPE);

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.

The springs read only the nearest theme scope's attributes, which Theme.tsx writes from the theme's root motion. An adaptation rule's tempo or character, and an enclosing theme's scaled durations, reach the CSS but not the springs: a when: {pointer: 'coarse'} rule with tempo: 1.3 makes --duration-medium 533 ms while springs.settle stays 0.5 s. spec:AST-108 FR3 lists an adaptation rule among its verified states, and FR4a needs a CSS fade and its spring to land together.

): Record<string, string> {
const knobs = resolveMotionKnobs(config);
const tokens: Record<string, string> = {};
if (knobs.tempoIsSet) {

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.

Only a theme that sets tempo writes --pre-tempo-duration-*, and custom properties inherit. Inside a tempo: 1.5 theme, a nested theme with motion: {fast: 100, medium: 250, slow: 600, ratio: 0.75} leaves its Spinner turning every 730 ms instead of 450 ms and its Skeleton pulsing over 550 ms instead of 335 ms (component:Spinner FR3, component:Skeleton FR4). Writing the pre-tempo twin wherever a theme writes a duration, with a nested-theme test, would fix it.

Comment thread packages/cli/assets/docs/motion.doc.mjs Outdated
items: [
"`tempo` scales every motion duration: 1 is Astryx's own, 0.75 the quickest and 1.5 the slowest (values outside are clamped).",
"`character` sets how lively motion is: `'crisp'` decelerates without overshoot, `'standard'` is Astryx's own, and `'expressive'` overshoots slightly on entrances and moves. Exits and fades never overshoot.",
"`reducedMotion: 'always'` reduces motion for everyone, as if each person had turned on Reduce Motion; `'system'`, the default, follows each person's setting. No value loosens Reduce Motion.",

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.

No core component reads data-astryx-reduced-motion yet: the 48 core files that key reduced motion on prefers-reduced-motion ignore it, so a person without the setting still gets full motion under an 'always' theme. spec:AST-108 FR6 requires those styles and script readers to key on the mark; until they do, this line and the changeset overstate what 'always' does.

Comment thread packages/core/src/theme/defineTheme.ts Outdated
input.motion != null && baseMotion != null
? {...baseMotion, ...input.motion}
: input.motion;
const lineageMotionTokens =

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 re-applies the base's explicit --duration-* and --ease-* tokens above whatever the child generates, so a child's own duration scale no longer beats a base's explicit token, as it does today. That is the INV15 clause waiting on an owner decision; if it stays, the record's System model precedence list should say so too.

* the element is in (`spec:AST-108` FR3): `tempo` scales a spring's duration
* and a role's duration, and `character` moves a spring's bounce by its
* role's weight (1 for entrances and presentations, 0.5 for state changes, 0
* for exits, which never overshoot). The theme's `MotionScaleConfig` alone

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.

"0 for exits, which never overshoot" contradicts spec:AST-108 FR3: a weight-zero role keeps its measured bounce, and springs.menuClose (damping ratio 0.85) overshoots by about 0.6%. The same wording is in motionKnobs.ts:33-34, defineTheme.ts:242-243, theme.template.ts:186 and motion.doc.mjs:159.

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
…empo and character) into the public animator

spec:AST-107 FR3 has every animate play on springs.stateChange as
spec:AST-108 FR3 resolves it at the theme's knobs; meta#8677 brings that
resolution, with main and meta#8661 beneath it.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
…ionAnimator

The public createAnimator (spec:AST-107 FR6) takes the name; meta#8677's
tests of the internal animator now import it by its own name.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
…coasts every channel

spec:AST-107 FR3 as merged:

- Every animate plays springs.stateChange at the tempo and character of
  the theme the element is in (spec:AST-108 FR3), which meta#8677 brings;
  the new case fails with the motion module as it was before it.
- decay coasts any of the five channels it is given on the fixed
  momentum decay (spec:AST-108 FR4): the translate axes as a throw that
  Reduce Motion lets continue, scale, rotate and opacity as a transition
  it plans like a move (design:reduced-motion DR2); opacity stops at 0 or
  1. It took only x and y before.
- destroy halts every channel where it shows before letting go, so
  nothing reads as moving after it, and every method after it does
  nothing; destroy removes every listener it added (FR6).

The docs and the changeset say the same.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
Main's golden counts 367 matches for "icon set"; this branch's public
animator pages and meta#8677's motion docs each add one, so the merged count
is 369. The listed results and every other field are unchanged, and the
diff names no icon set and nothing experimental.
@vjeux
vjeux marked this pull request as ready for review November 5, 2026 12:28

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

Approved pending CI: CI has not run on this head yet, so a maintainer needs to add the ci:run label for it to run.

The springs, role durations and layout glide now follow a theme's motion.tempo as the current theme-motion records describe, and the glide's retiming under a mouse carries the owner's interim decision quoted in the description. Two small follow-ups, neither blocking: the theme authoring contract's known-gap line still says no spring reads the motion knobs, and the new themeTempo.ts header could be trimmed to its reason and constraints.

[Automated review]

Comment on lines +9 to +15
* A theme that sets `tempo` writes it, clamped, to the private custom
* property `--_astryx-motion-tempo` beside its duration tokens. An
* adaptation rule writes it where its condition holds, and a nested theme
* that sets no tempo inherits it. A layer whose durations come from a
* duration scale writes a tempo of 1, so its springs keep its own pace. One
* read of a custom property forces no layout.
* Nothing here imports a theme module, runs on import or runs on the server.

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: this header runs 18 lines, past the source-comment guide of about 12. Suggest keeping only: "A theme writes its tempo to --_astryx-motion-tempo; a duration-scale layer writes 1, and a nested theme inherits it. Reading a custom property forces no layout. Nothing here runs on import or on the server."

return null;
}
// The motion role it plays: its table value (DEC-15).
// The motion role it plays, at the tempo of the theme it is in.

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: this comment restates the resolveMotion call below it; it can go.

@vjeux
vjeux marked this pull request as draft November 5, 2026 13:25
@cixzhang

cixzhang commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 9e704d9

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

Approved pending CI: CI has not run on this head yet. This pull request is a draft from a fork, so once it is marked ready for review, a maintainer adds the ci:run label so CI runs.

A theme's motion.tempo now times the animator's role springs, role durations and the layout glide, as the theme motion and animated-layout specs require, and the glide's retiming under a mouse follows the owner's interim decision quoted in the description. The two tests now calling createMotionEngine pass on this head.

Non-blocking, when convenient:

  • The theme authoring contract's known-gaps line "no spring reads the motion knobs yet" is out of date once this lands; a record owner can update it.
  • The new themeTempo.ts header and the changed resolveMotion.ts header run past the source-comment guide of about 12 lines, and the comment above resolveMotion in animator.ts restates the call.

[Automated review]

@vjeux
vjeux marked this pull request as ready for review November 5, 2026 13:54
@cixzhang cixzhang added the ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) label Nov 5, 2026
@cixzhang
cixzhang marked this pull request as draft November 5, 2026 14:21
@cixzhang
cixzhang marked this pull request as ready for review November 5, 2026 14:21
CI's formatting check reads two lines that Prettier writes differently.

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

Changes requested: one formatting fix, and nothing else blocks.

  • Miss: packages/core/src/motion/resolveMotion.tempo.test.ts (lines 86-89) is not formatted with the repository's Prettier, so the required lint check's changed-file formatting step will fail; run pnpm exec prettier --write packages/core/src/motion/resolveMotion.tempo.test.ts.

The springs, role durations and layout glide now follow a theme's motion.tempo as the theme motion and touch presentation specs require, and the glide's retiming under a mouse follows the owner's interim decision quoted in the description. CI has not run on this head yet: once the draft is marked ready for review, a maintainer adds the ci:run label so it runs.

Non-blocking, when convenient:

  • The theme authoring contract's known-gap line "no spring reads the motion knobs yet" is out of date for tempo once this lands; a record owner can narrow it to character.
  • The new themeTempo.ts header runs past the source-comment guide of about 12 lines, and the comment above resolveMotion in animator.ts restates the call.

[Automated review]

Comment on lines +86 to +89
expect(slowed.value(seconds * 1.5)).toBeCloseTo(
base.value(seconds),
6,
);

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.

The repository's Prettier puts this call on one line, so the lint check's changed-file formatting step fails on this file. pnpm exec prettier --write packages/core/src/motion/resolveMotion.tempo.test.ts fixes it.

One call now fits on a line, as Prettier writes it.

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

Approved pending CI: CI has not run on this head yet. The pull request is a draft from a fork, so once it is ready a maintainer adds the ci:run label for CI to run.

The formatting issue from the last review is fixed, and nothing else blocks. The springs, role durations and layout glide now follow a theme's motion.tempo as the theme motion and touch presentation specs require, and the glide's retiming under a mouse follows the owner's interim decision quoted in the description.

Non-blocking, when convenient:

  • The theme authoring contract's known-gap line "no spring reads the motion knobs yet" goes out of date for tempo once this lands; a record owner can narrow it to character.
  • The new themeTempo.ts header runs past the source-comment guide of about 12 lines, and the comment above resolveMotion in animator.ts restates the call.

[Automated review]

@vjeux
vjeux marked this pull request as ready for review November 5, 2026 15:59

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

Approved pending CI: CI is still running on this head.

Nothing blocks. The springs, role durations and layout glide follow a theme's motion.tempo as the theme motion and touch presentation specs require, and the glide's retiming under a mouse follows the owner's interim decision quoted in the description.

Non-blocking, when convenient:

  • Miss: the changeset names the touch springs and the glide, but createAnimator and useAnimator's animate now follow tempo too, under every pointer; one sentence there would tell app authors.
  • The theme authoring contract's known-gap line "no spring reads the motion knobs yet" goes out of date for tempo once this lands; a record owner can narrow it to character.
  • The new themeTempo.ts header runs past the source-comment guide of about 12 lines, and the comment above resolveMotion in animator.ts restates the call.

[Automated review]

@cixzhang
cixzhang merged commit ceefccd into meta:main Nov 5, 2026
99 of 105 checks passed

This branch was successfully deployed

1 active deployment
Preview — e4a2b95f Deployed Nov 5, 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