Repository navigation
Conversation
Adds internal/interaction-harness (@astryxdesign/interaction-harness) and playwright.interaction.config.ts: phone-sized touch projects in Chromium and WebKit for *.interaction.browser.spec.ts specs. Touch scripts are data (tap, drag with hold and rest, flick with an exact release velocity and velocity profiles, hold-then-drag, pinch, rotate, any path, a second finger mid-gesture), played as real engine input: CDP Input.dispatchTouchEvent with scripted timestamps in Chromium, WebKit's own input command in WebKit. The harness's self-test proves the timestamps, a native fling and multi-touch delivery; a pr-interaction CI job runs the proofs against each PR's Storybook build, and a container script runs the WebKit project where host libraries are missing.
Springs, scroll physics, timing curves, navigation values and gesture thresholds for the touch presentations of core components, each with its source (Apple documentation, a WWDC session, WebKit's UIKit-matched source, or a measurement of UIKit on the iOS 26.2 simulator) or labelled a guess with its measurement task. A test enforces the source rules on every domain file, in their JSDoc and typed forms, and checks the list of simulator measurements still owed.
Pure functions for the touch presentations: a closed-form damped spring with every Apple parameterization and Core Animation's settling duration, a spring chasing a moving target, UIScrollView's coast, rubber band and release projection. Tests hold them to an RK4 integration, WebKit's spring solver, the WWDC conversion equations and values read from UIKit, and play the constants table through them.
…rena An internal gesture system for touch presentations, not exported from the package. GestureRecognizer carries UIKit's states and allowed transitions, discrete and continuous recognizers, require-to-fail, simultaneous recognition on request, shouldBegin and shouldReceiveTouch, cancelsTouchesInView and the touch delays. GestureArena settles each touch sequence without the DOM: participants, resolution order, claims, delivery to the view, click dispositions (allow, suppress, defer and replay) and timers as deadlines on the event clock. Its rules are tested with scripted recognizers, independent of any real one.
…sholds Tap recognizes at the lift within 45 pt of movement, taps 0.35 s and 45 pt apart, and a 1.5 s hold; long press begins at exactly touch-down + 0.5 s on the event clock within 10 pt, even when the main thread was blocked, and a zero duration begins on the touch itself. The arena tests with these real recognizers cover tap against double tap, delegate requirements, deferred clicks and cancellation.
Touch behaviours key on a coarse primary pointer, layout behaviours on a compact viewport, and presentations that need both (a sheet in place of a popover) ask for both. utils/touchPresentation.ts holds the three queries, SSR-safe hooks (useTouchPresentation; useMotionPresentation for components with no interaction to infer from: UIKit motion under a finger, theme tokens under a mouse) and a one-moment read. Styles write the same queries as StyleX media keys, and a test keeps core's compact-touch keys spelled as the shared query. A compact viewport is narrower than 744 px or shorter than 500 px, the closest a size gets to UIKit's compact size class: an iPad mini in portrait keeps the anchored popover and a phone in landscape gets the sheet; a Max-size iPhone in landscape is the known miss. The adaptive popover-or- sheet presentation reads the shared query; its docs, stories and tests follow.
A file that reads only defineConsts values from a .stylex.ts module lost
that import under the legacy {stylexOptions} form Storybook uses: the
babel wrapper's StyleX instance ran without treeshakeCompensation (the
unplugin applies its Vite default only to its own instance, which runs
after the wrapper), so once stylex.create was compiled the TypeScript
transform dropped the import and the defining module never reached the
build. A constant used as a media-query key failed the build with
"Invalid empty selector"; one used as a value shipped as a custom
property nothing declares. The wrapper now gets treeshakeCompensation by
default.
The layer split also substituted constants only within each layer, so a
constant defined on the library side and read on the product side stayed
a placeholder in either form; every layer now sees every constant.
A production build now warns, naming the variable and the rule that reads
it, when the StyleX stylesheet reads a generated custom property nothing
declares; undefinedVariables: 'error' fails the build instead, and this
repo's Storybook and Sandbox configs set it so CI catches the next case.
Pure functions for the touch presentations: a closed-form damped spring with every Apple parameterization and Core Animation's settling duration, a spring chasing a moving target, UIScrollView's coast, rubber band and release projection. Tests hold them to an RK4 integration, WebKit's spring solver, the WWDC conversion equations and values read from UIKit, and play the constants table through them.
createAnimator plays springs and UIScrollView coasts as Web Animations keyframes on translate, rotate, scale and opacity, on one explicit clock, so they keep running while JavaScript is busy. Presentation values and velocities come from the curves, so a finger can catch any animation and a new target keeps the velocity; the end state is committed inline and held by the keyframes through a main-thread block, and a finished animation retires when its own time reaches the curve's end, so a sampler can pause it; Reduce Motion substitutes UIKit's cross-fade.
… Chromium The Chromium driver sent a lift of one finger of several as a touchMove listing the fingers still down, assuming CDP releases a point a move omits. It does not: the lifted finger stayed down. A touchEnd that lists the lifting fingers releases exactly those while the others keep moving, so the driver sends that, and a self-test lifts one of two fingers and checks the release, the other finger's moves and the touchend's remaining touches. Playwright's WebKit sends that touchend with no remaining touches, which iOS never does. The harness does not work around it: drivers expose partialLifts, and PARTIAL_LIFT_SKIP is the reason a proof cites when it skips; the README lists it with the other engine limits.
Pause every Web Animation, seek it to a time and read the presented transform and opacity, then compare the curve with oracles that share no code with the motion under test: cubic-bezier, the SwiftUI spring (response, dampingFraction, initial velocity), UIScrollView deceleration and UIKit's rubber band. Storyboards photograph seeked animations at t = 0, 16.7, 33.3 ... ms or each step of a scripted touch, and write a PNG contact sheet plus the JSON trace a spec asserts, under the gitignored test-results/interaction-evidence. A timer gate holds a component's own timers while a filmstrip is taken. pnpm storyboards runs the tagged tests.
…sented values An animation that keeps its end value in a run of identical final keyframes (held so a later interruption can read it) reported its whole duration as the curve: storyboards filmed the hold, waitForAnimation's interval reached past the landing, and waitForRest, which also required every animation to leave the running state and read only transform, timed out. The curve now ends where that run starts (HeldAnimation.curveEndMs, waitForAnimation's to, the default storyboard times), and rest is no animation pending or before its curve's end plus the presented transform, translate, rotate, scale and opacity (and ::backdrop opacity) still for 3 frames, whatever the playState. sampleCurve composes translate, rotate and scale with transform, so curves of the individual properties are sampled.
Timestamped composited frames (Chromium's screencast and WebKit's own), a marker the frames can find, and the busy-main-thread proof: block the page's main thread mid-flight and show the element still moving every frame, with a requestAnimationFrame animation that freezes as the negative control. Chromium trace counts of Layout, Paint and style work inside an animation window, and long tasks. The exemplar spec proves the BottomSheet drag to a detent with all of it: the drag follows the finger, a flick dismisses, the settle matches its timing function, keeps advancing through a blocked main thread in Chromium and WebKit, does no Layout or Paint, and gets a drag and settle storyboard.
…ed machine The proof asserted a stall under 40 ms and near-exact frame counts, so a dropped screencast frame or two on a loaded runner failed it, and frames delivered out of order read as the element stepping backwards. busyWindowProof now returns a verdict that tolerates what load does to a screencast and nothing a frozen animation does: a median frame interval of at most 20 ms, no stall longer than three screencast intervals, and at least 90% of the window's whole frame intervals presenting a new position (one miss always allowed), with the raw numbers in a summary. Screencast frames are sorted by presentation time, the cadence comes from the frames around the window, and blockMainThread can block from inside the page in the frame an animation has run, so a short settle is still judged at length on a busy machine. The exemplar judges its settle until it is within 2 px of rest, since the screencast rounds to whole pixels. A unit test covers the limits; the rAF negative control still fails every criterion.
The pan begins past 10 pt on either axis and leaves that travel out of its translation, as UIPanGestureRecognizer does; axis and one-way directions with axisRatio, axis lock, finger changes without jumps, scrollEdge, and an opt-in non-UIKit scrollHandoff. VelocityTracker defaults to UIKit's filter (0.2 x the newest event's velocity + 0.8 x the previous one's), which reproduces every value UIKit reported on recorded streams.
…zers The rest of UIKit's recognizer set on the gesture system: swipe recognized during the motion, pinch measured from the starting span moved by its hysteresis, unwrapped rotation, edge pans resolved against the writing direction that fail at touch-down outside their band, and hover for mice and hovering pens.
The touch-action an element gets from its recognizers (pinch-zoom stays unless a pinch or rotation recognizer is attached), what the scrollers under a touch could still do along the scroll chain, and UIKit's haptic feedback kinds on the web: navigator.vibrate where it exists, the switch tap haptic in iOS Safari.
…s takeover One host per document routes fingers (touch events, paired with their pointer events), mice and pens to the recognizers on the hit path. A recognizer that owns a touch cancels its touchmove through a non-passive listener; pointercancel, an uncancelable move or a scroll mean the browser took the touch; a claimed touch's touchend is cancelled and its click swallowed, a deferred click replayed when its recognizer fails. pointerIdOf names the pointer behind a tracked contact, so a held finger can be handed to code that follows pointers.
… and hooks PressTracker applies UIControl's events and its 70 pt tracking margin, the content-touch delay inside scrollers, the quick-tap flash and the scroll brake, with two profiles: control (UIButton) and cell (a row: delayed highlight everywhere, cancelled past the pan hysteresis, no flash). paintPress is the one writer of data-astryx-press and never clobbers a held row. installPressAttribute paints every marked surface with one tracker per touch, picks the profile from the marker's value and releases on its clock. useGestureRecognizer attaches a recognizer with a stable identity and no render per move.
The same touch scripts run in Chromium (CDP touches with timestamps) and WebKit (its own touch input): touch-action per element, claims over a scroller, no ghost click, scroll edge, pinch on its element only, a second finger, and a long press at exactly 500 ms under a blocked main thread. The README documents the system for component authors.
…tarts at the commit
play() bakes curves given as value and velocity functions of time (a spring
chasing a moving target, a fitted UIKit curve) like a spring.
createAnimator(dialog, {pseudoElement: '::backdrop'}) drives a
pseudo-element: a static filling animation holds its end value.
animatorFor(element) shares one animator per element, so callers animating
different axes compose in one translate track; atRest() waits out every
curve on its properties, and rebase() moves them by a layout shift keeping
their springs. A call from rest gets no start time, so the browser starts it
at its commit, as Core Animation does, and handle.started reports that
start. Every animation and set() promotes its channels to a compositing
layer in the same task (will-change, or a host custom property for a
pseudo-element) until 500 ms after the last motion: Mobile Safari only runs
a script-started curve through a block on an element that already has a
layer. Under Reduce Motion the substituted cross-fade can follow a
presentation's own spring, and crossfadeAs gives a slide that changes no
opacity its direction. The header lists the iOS Safari limits measured on
the simulator.
…eraction harness useAnimator wraps the animator for React without re-rendering per frame. Browser proofs on the interaction harness, in Chromium and WebKit on the phone profile: a spring keeps advancing on every screencast frame through a 1 s main-thread block and every frame sits on its analytic curve; a spring ending inside the block keeps its end state; no Layout or Paint runs while it animates; the engine plays the curve when seeked; a finished animation leaves within a frame or two of the curve's end; a dialog's ::backdrop follows the curve.
A touch presentation is now also an idiom: ios for WebKit on iOS or iPadOS
under a coarse pointer (CSS.supports('-webkit-touch-callout: none'), which
no other engine implements), default everywhere else, Android included,
which keeps today's behaviour until a Material variant exists.
useTouchPresentation and readTouchPresentation report it, useTouchIdiom
reads it alone, and useMotionPresentation now chooses UIKit motion only in
the iOS idiom. Styles gate the same way with two nested StyleX keys, the
media query outside and the supports query inside (IOS_TOUCH_STYLE_KEYS),
as core's 16px input floor already does; a test keeps every such key in
core spelled and nested that way. A real-engine spec reads Chromium as an
Android phone and WebKit as default.
Every spring, duration (a key ending in Ms) and curve of a motion table is
a motion role named by its table path (springs.sheetPresent,
timing.curves.easeInOut, sheet.present). defineTheme({motion}) takes
springs, durations and curves keyed by role, at the root or in an
adaptation rule, and writes one CSS variable per role it sets
(--motion-springs-sheet-present: 0.6s 0.1); a role it leaves out keeps the
table's measured value. The duration scale becomes all-or-nothing so a
theme can set roles alone. Adaptation rules gain a platform condition (ios
or other) that lowers to @supports (-webkit-touch-callout: none) around the
media block. Gesture thresholds and the arbitration timings (the
content-touch delay, the scroll brake) are not roles.
The motion docs carry a table of every role with its CSS variable and
default, generated from the tables (pnpm generate:motion-roles, checked in
check:repo).
# Conflicts: # packages/core/src/motion/README.md
…me sets it resolveMotion(element, role) reads a motion role's CSS variable from the element's theme scope (the nearest [data-astryx-theme], or the root) when a motion starts: a style read, no layout, cached per scope until a theme attribute changes, theme CSS is inserted or edited, the window resizes or a pointer, contrast or reduced-motion preference changes. An empty or malformed value, a detached element or a value that is not a role gives the table's value back. The animator resolves every spring it is handed, the Reduce Motion fade's duration, curve and spring, and a decay's edge bounce, so call sites keep passing table entries. A browser proof seeks two boxes animated with springs.sheetPresent, one inside a theme that sets it: each plays its own spring within 0.05 px in Chromium and WebKit.
…tal touch exports
15d28c1 to
5ca6422
Compare
5ca6422 to
f25b7c2
Compare
…tunes Since nothing registers a table on import, the proof that a theme retunes a table spring must register the springs table, as a touch module would. It checks that the role is there before measuring, so a proof that resolves a role nothing registered fails with that cause instead of a curve mismatch.
A decay only continues the velocity a finger handed it on release, so it keeps its motion, as a native scroll view's momentum does; a spring a component starts on release toward a target it picks is a transition and cross-fades (DR2). The animator's changeset names the design:reduced-motion policy instead of the spring fade it replaced. The same statement lands in meta#8056, where the animator introduces Reduce Motion.
Theme adaptations have no platform condition, and defineTheme rejects
when: {platform}, so the template's adaptation axes are pointer precision,
contrast and motion preference, as on main.
There was a problem hiding this comment.
Approval needs an engineering owner's decision first. spec:AST-094 is a draft with no owner approval, and its FR4 and DEC-1 say touch motion is not themable, yet this branch adds theme motion roles and makes the press paint's timers follow them. An engineering owner settles that, either by amending AST-094 FR4 to allow motion roles or by dropping the roles and the theme-following timers, and records the decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so if they are an engineering owner. These changes are needed whatever is decided:
- The delegated press consumer never calls
registerTimingTable(), soresolveMotionreturns the table's values and the new test indelegatedPress.test.tsfails; register the table ininstallPressAttribute. - The diff against
maincarries 30,605 authored lines because the predecessor pull requests are unmerged; land them first, or rebase so this pull request carries only its own commit. - The CSS release still runs the fixed 200 ms
pressConsts.releaseMs, so a theme-following timer cannot match it; have the animation read the same role or dropreleaseRole, and correct the comment, README and Changeset. spec:AST-094FR8 contradicts the 200 ms cap indesign:reduced-motionDR2, FR7 amends aspec:AST-090that does not exist, and the theme template still lists the removedplatformadaptation axis.
[Automated review]
|
|
||
| import {useEffect, useState} from 'react'; | ||
| import {resolveMotion} from '../motion/resolveMotion'; | ||
| import {timing} from '../motion/uikit/timing'; |
There was a problem hiding this comment.
resolveMotion reads a role only for a registered table (motion/resolveMotion.ts:13-15), and nothing on this path calls registerTimingTable(), so both lookups below return the table's 250 ms, 200 ms and 100 ms whatever the theme sets. The new test in __tests__/delegatedPress.test.ts fails on this head for that reason and passes once the table is registered first. Calling registerTimingTable() (it is idempotent) in installPressAttribute, as motion/reducedMotion.ts does before it resolves, fixes it.
| on: a transition that travels becomes a cross-fade carrying the same change | ||
| of state. An entrance lands in place and fades in, an exit fades out in | ||
| place, and any other move fades through. A navigation push or pop | ||
| cross-fades over 0.5 s on the ease-in-out curve; a sheet, a full-screen |
There was a problem hiding this comment.
design:reduced-motion DR2 (current) requires every Reduce Motion transition to change opacity only and finish in 200 ms or less, and DR4 rules out a longer animation; the animator here follows it (timing.reducedMotionCrossFadeMs is 200). A 0.5 s navigation cross-fade and sheet fades on their presentation springs contradict that record, which FR8 never names. Defer FR8 to design:reduced-motion, or mark it as a proposed amendment of DR2.
| follows. | ||
| - **FR7 — Exits animate as UIKit's do.** A touch presentation MUST play the | ||
| exit its UIKit counterpart plays. **Proposed; pending owner review:** this | ||
| departs from the draft `spec:AST-090` FR1 (exits off by default) and FR10 |
There was a problem hiding this comment.
There is no spec:AST-090 on main, so this amendment (and the one under Current-state impact) cannot be ruled on. Cite the record by its real id, or drop the amendment.
| * `from` includes its point; `below` excludes it. The other supported axes | ||
| * are primary-pointer precision, contrast preference, and motion preference. | ||
| * are primary-pointer precision, contrast preference, motion preference, and | ||
| * platform (`ios` for the browsers of iPhone and iPad, `other` for the rest). |
There was a problem hiding this comment.
ThemeAdaptationCondition no longer has platform, and defineTheme refuses it (packages/core/src/theme/expandMotionRoles.test.ts checks this), so this line sends theme authors to an error. Restoring the base sentence fixes it.
There was a problem hiding this comment.
Approval needs an engineering owner's decision first. spec:AST-094 is a draft with no owner approval, and its FR4 and DEC-1 say touch motion is not themable, yet this branch adds theme motion roles and makes the press paint's timers follow them; an engineering owner settles that, either by amending FR4 to allow motion roles or by dropping the roles and the theme-following timers, and records it by commenting /approve-spec <full head SHA> on this pull request (the author may do so if they are an engineering owner). Registering the timing table fixed the earlier test failure; these changes are still needed whatever is decided:
- The release animation still runs the fixed 200 ms
pressConsts.releaseMs, so a theme-followingfadingtimer cuts the fade short under a shorter value and lingers under a longer one; have the animation read the same role or keep the table's release, and correct the comment, README and Changeset. - The diff against
maincarries 30,609 authored lines because the predecessor pull requests are unmerged; land them first, or rebase so this pull request carries only its own commits. spec:AST-094FR8's 0.5 s and spring-timed fades contradict the 200 ms cap indesign:reduced-motionDR2, and FR7 andscripts/sync-exports.jscite records that do not exist (spec:AST-090, "AST-094 OQ4").- The theme template still lists
platformas an adaptation axis, whichdefineThemenow refuses.
[Automated review]
| releaseMs: | ||
| profile.releaseRole == null | ||
| ? profile.releaseMs | ||
| : resolveMotion(element, timing.press, profile.releaseRole), |
There was a problem hiding this comment.
This timer now follows the theme's timing.press role, but the release it gates still runs pressConsts.releaseMs (200 ms, utils/interactionOverlay.stylex.ts), and no stylesheet reads --motion-timing-press-*. Under a theme value below 200 ms the highlight snaps off mid-fade; above it, fading outlives the fade. Have the release animation read the same role, or keep profile.releaseMs until it does (the header comment, README and Changeset make the same "matches the CSS fade" claim).
| on: a transition that travels becomes a cross-fade carrying the same change | ||
| of state. An entrance lands in place and fades in, an exit fades out in | ||
| place, and any other move fades through. A navigation push or pop | ||
| cross-fades over 0.5 s on the ease-in-out curve; a sheet, a full-screen |
There was a problem hiding this comment.
design:reduced-motion DR2 (current) requires a touch transition under reduced motion to change opacity only and finish in 200 ms or less, and DR4 rules out a longer animation; the animator in this branch follows it (timing.reducedMotionCrossFadeMs is 200). A 0.5 s navigation cross-fade and sheet fades on their presentation springs contradict that record, which FR8 never names. Defer FR8's timing to design:reduced-motion, or mark it a proposed amendment of DR2 and DR4.
| follows. | ||
| - **FR7 — Exits animate as UIKit's do.** A touch presentation MUST play the | ||
| exit its UIKit counterpart plays. **Proposed; pending owner review:** this | ||
| departs from the draft `spec:AST-090` FR1 (exits off by default) and FR10 |
There was a problem hiding this comment.
There is no spec:AST-090 on main, so this amendment (and the one under Current-state impact) cannot be ruled on. Cite the record by its real id, or drop the amendment.
| * `from` includes its point; `below` excludes it. The other supported axes | ||
| * are primary-pointer precision, contrast preference, and motion preference. | ||
| * are primary-pointer precision, contrast preference, motion preference, and | ||
| * platform (`ios` for the browsers of iPhone and iPad, `other` for the rest). |
There was a problem hiding this comment.
ThemeAdaptationCondition has no platform, and defineTheme refuses it (packages/core/src/theme/expandMotionRoles.test.ts asserts this), so this line sends theme authors to an error. Restoring the base sentence fixes it.
| @@ -177,6 +190,13 @@ const STATIC_EXPORTS = { | |||
| types: './dist/utils/interactionOverlay.stylex.d.ts', | |||
| default: './dist/utils/interactionOverlay.stylex.js', | |||
| }, | |||
| // Proposed; pending owner review (spec:AST-094 OQ4): the Reduce Motion | |||
There was a problem hiding this comment.
spec:AST-094 has only OQ1 and OQ2, so "OQ4" points at nothing, and this subpath sits outside the experimental boundary the two subpaths above use. Name the governing record, or ship these constants through an experimental subpath.
…cription They ran under their own Playwright config, which no CI job picks up; spec:AST-030 FR11 asks new coverage to join its existing owner. Their results (Chromium and WebKit, and Mobile Safari on the iOS 26.2 simulator) stay in the pull request description, and the unit suites keep the resolver's answers.
Nothing pinned useMenuDrillIn's theme read: a fixed default query left the suites green. A 900 px coarse screen now flies the sub-menu out under the default md (768) and drills in under a theme whose md is 1000, as the root sheet does (spec:AST-012 FR9).
… reads Its default ignored a theme that moves md; nothing calls it yet, so the compact definition is now required and its first caller passes the theme's (useCompactDefinition()). The changeset names ContextMenu's sub-menus, whose drill-in changes too.
…l subpath The package no longer exports @astryxdesign/core/motion/experimental or @astryxdesign/core/gestures/experimental: a file path alone does not mark an API as experimental, and a public experimental API needs explicit marking in the API itself and its own spec decision. Core components import motion and gestures from ../motion and ../gestures, and Lab through internal workspace imports. Removes the two entry modules and their tests, their package.json and sync-exports entries, the hook docs whose import path was a removed subpath (useAnimator, useGestureRecognizer, usePressTracker, useTouchPresentation; already left out of search and the docsite), and the subpaths changeset. The READMEs and the gestures barrel say the modules are internal.
… into the touch foundation # Conflicts: # packages/core/src/utils/touchPresentation.ts
The animator and reducedMotionFade() read the person's settings (prefers-reduced-motion and a coarse primary pointer) and nothing else: the prefersReducedMotion and isCoarsePointer options are gone, so no caller can keep the motion or change which form it takes (design:reduced-motion DR4), and the experimental motion surface adds no Reduce Motion override. Tests set the settings through matchMedia. Co-authored-by: vjeux <197597+vjeux@users.noreply.github.com>
…hangesets say internal again The proposed ./motion/reducedMotion.stylex export goes: its constants carry no experimental marking and had no spec decision, and Reduce Motion stays internal (design:reduced-motion). Its Lab import test goes with it. With the experimental subpaths gone, the touch pieces' changesets say they are internal and not exported again, and search and the docsite no longer need to skip experimental hook docs.
A static import graph of core's source fails when anything imports a table as a namespace object (which keeps every table, register function and the role registry), when shared code imports a table that belongs to one component, or when a component reaches a component's table only through the uikit barrel or shared code. A made-up package proves each case fails.
Motion and gestures are internal to core, so Lab reaches them through workspace imports of their module paths (@astryxdesign/core/motion/animator, @astryxdesign/core/gestures, @astryxdesign/core/utils/touchPresentation), which core's exports do not list. Vitest and Storybook already alias @astryxdesign/core to its source; Lab's typecheck and declaration build now map those paths to core's built types the same way.
PRESS_PROFILES name the timing.press motion roles behind their release and flash clocks (releaseRole, flashRole). The delegated press consumer resolves them under the pressed element's theme with resolveMotion, the flash when the touch lands and the release when it starts, so the fading timer lasts as long as the CSS fade the theme sets. The content-touch delay and the scroll brake stay table constants: they arbitrate, they do not move anything.
…esolves its roles Under the motion-table registry (meta#8076: nothing registers on import, the touch code registers the tables it animates with), resolveMotion found no role for timing.press and returned the table's value, so the release fade ignored the theme's press timings. installPressAttribute now calls registerTimingTable() (idempotent) before any touch resolves them.
…'s gesture changesets are
597fc21 to
0d10ef3
Compare
There was a problem hiding this comment.
Approval needs an engineering owner's decision first: no current spec governs the theme-following press timers or the defineTheme({motion}) roles this branch ships, because spec:AST-094 (FR4 and FR11) is still a draft in #8316. An engineering owner settles that by providing or identifying the governing spec, for example by approving and merging AST-094 before this lands, and records the decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so as an engineering owner. Whatever is decided, these changes are still needed:
- The release timer now follows the theme, but the
fadinganimation still runs the fixed 200 mspressConsts.releaseMs, so make the animation read the same role (or keep the table's release) and fix the comments, README and Changeset that say they match. VelocityTrackerhas no stillness reset and keeps selectable filters, which currentspec:AST-100FR2 andarchitecture:interaction-modalityINV10 rule out.- The diff against
maincarries about 29,700 lines of unmerged predecessors around a 75-line change, so land them first or rebase onto them.
[Automated review]
Superseded by #8682, which adopts its rule red-first; closing in its favor. The branch stays.
Superseded by #8682, which adopts its rule red-first.
Depends on #8076 (and its predecessors, at a71f4e5); review the last commit.
Primary intent
Make the delegated press paint's timers follow the theme, so a row's or a button's
fadingstate lasts exactly as long as the CSS fade its theme sets.User or maintainer impact
Themes retune touch motion through motion roles (#8257, #8258), including the press release (
timing.press.buttonReleaseMs,.highlightOutMs) and the quick-tap flash (.flashMs), and pressable styles read those variables. The delegated press consumer (#8073) still timedfadingwith the table's numbers: under a theme that lengthens the release, the attribute went away mid-fade and the highlight snapped off; under one that shortens it, the row stayedfadingafter its fade ended.Problem and solution fit
Deltas:
PRESS_PROFILESname the motion role behind each clock:releaseRole(buttonReleaseMsfor a control,highlightOutMsfor a cell) andflashRole(flashMs, controls only). A profile without them keeps its numbers.resolveMotion: the flash when the touch lands, the release when it starts.Authority
spec:AST-094FR11 (two press profiles) and the theme motion roles of #8257. Proposed; pending owner review.Change and evidence
--motion-timing-press-button-release-ms: 400ms, a released row staysfadingfor exactly 400 ms; with--motion-timing-press-highlight-out-ms: 320ms, a cell for 320 ms. Without this change the same test fails: the attribute goes at 250 ms.Scope
Testing
Restacked on feat(core): the internal touch foundation: press tracking and the animator #8076 @a71f4e51 (motion and gestures internal):
tscfor core and lab, changesets, sync-exports and check:sync clean.pnpm exec vitest run --project ui packages/core/src/gestures packages/core/src/motion: 592 tests passed.tsc --project packages/core/tsconfig.json --noEmit,ASTRYX_STRICT_LINT=1 eslinton the changed files: clean.pnpm check:changesets, changeset coverage,pnpm check:sync: clean.Size: 5 files, 67 lines added, 4 removed.
Stack (merge in this order): #8052 spec → … → #8074 gesture system → #8076 internal motion and gestures → this pull request (beside the gesture line #8270 → #8309 → #8315 → #8336 → #8320 on #8076).