Repository navigation
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
PR Analysis Report
Generated by PR Enrichment workflow | View current CI run |
There was a problem hiding this comment.
This ProgressBar touch change needs an engineering-owner decision before it can be approved: it rests only on the draft spec:AST-094, so an engineering owner, the author included, can record approval of that direction (the touch indeterminate sweep, its Reduce Motion pulse and its timing outside the theme's motion tokens) by commenting /approve-spec <full head SHA>, or point to another governing spec. Whatever that decision, two changes are needed:
- Land the stacked predecessors first (or retarget) so this pull request carries only its own 461-line commit instead of 16,939 authored lines.
- Under a coarse pointer, a value that reverses to the one it just left animates in half the stated time (400 ms instead of 800 ms), because CSS shortens reversed transitions.
[Automated review]
| transitionProperty: 'transform', | ||
| transitionDuration: { | ||
| default: durationVars['--duration-medium'], | ||
| '@media (pointer: coarse)': 'var(--_progress-bar-duration, 0s)', |
There was a problem hiding this comment.
Under a coarse pointer, a change that reverses to the value it just left runs at half the duration written here. CSS shortens a transition that reverses to its own start value, so 10 → 90 paused at 50% and sent back to 10 runs 400 ms although --_progress-bar-duration is 800 ms (433 ms when reversed live at 52%); a retarget to any other value keeps its full duration. Keep reversals off the browser's reversing path, or document that they follow the drawn distance, and add the case to ProgressBarTouch.a11y.chromium.spec.ts.
| 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.
spec:AST-090 is not in this head or on main (it is proposed in open #8003), so FR7 asks owners to amend text that is not in the repository. Land AST-090 first, or restate FR7 against records present at the head.
6efa969 to
33a8e0f
Compare
There was a problem hiding this comment.
This ProgressBar touch change needs an engineering-owner decision before it can be approved: it rests only on the draft spec:AST-094, so an engineering owner (the author included, if they are one) can record approval of that direction (the touch sweep, its trackless Reduce Motion pulse and its timing outside the theme's motion tokens) by commenting /approve-spec <full head SHA>, or point to another governing spec. Whatever that decision, these changes are needed:
trackIndeterminatesetsdefault: null, which removes the grey track from every indeterminate ProgressBar, with a mouse too; keep the default colour in the override and test the mouse path.- Land the stacked predecessors first (or retarget) so this pull request carries only its own 500-line commit instead of 16,976 authored lines.
- Under a coarse pointer, a value that reverses to the one it just left animates in half the stated time (400 ms instead of 800 ms).
- AST-094 FR7 amends
spec:AST-090, which is not in the repository.
[Automated review]
| // fill's rules under it; the compiled CSS kept both.) | ||
| trackIndeterminate: { | ||
| backgroundColor: { | ||
| default: null, |
There was a problem hiding this comment.
stylex.props() merges by property, so this backgroundColor (a null default plus one media key) replaces track.backgroundColor whole: every indeterminate track loses --color-background-muted, with a mouse and with a finger, not only under coarse pointer plus Reduce Motion. On CI's static Storybook build of this head the Indeterminate story's track computes to rgba(0, 0, 0, 0) with a mouse, against rgb(212, 212, 212) on the base. Keeping the default in the override (default: colorVars['--color-background-muted']) and asserting the track colour on the mouse and touch-sweep paths would fix and guard it.
There was a problem hiding this comment.
This ProgressBar change needs an engineering-owner decision before it can be approved: it rests only on the draft spec:AST-094, so an engineering owner (the author included, if they are one) can record approval of that direction for the iOS bar (the quarter-track sweep, the trackless Reduce Motion pulse and its theme-retunable timing) by commenting /approve-spec <full head SHA>, or point to another governing spec. Whatever that decision, these changes are needed:
trackIndeterminatekeepsdefault: null, which removes the grey track from every indeterminate ProgressBar, with a mouse and on Android too; keep the track colour as the default and test those paths.- Land the stacked predecessors first (or retarget) so this pull request carries only its own two commits instead of 19,658 authored lines.
- The iOS gate infers the engine from
-webkit-touch-callout, whichspec:AST-013FR6 does not allow, and the themeplatformcondition is outsidespec:AST-012FR2's closed vocabulary, so each needs its record amended first or has to go. - AST-094 FR4 and DEC-1 say touch motion is not themable while the new theme motion roles retune it, the motion docs advertise roles no component reads, and FR7 amends
spec:AST-090, which is not in the repository. - On iOS a fill that reverses to its previous value runs at half its written duration,
lintandtest-node (1/4)fail on a missing@astryxdesign/clichangeset and a stale docs golden, and the description needs AST-094's writing-rubric results.
[Automated review]
| default: durationVars['--duration-medium'], | ||
| '@media (pointer: coarse)': { | ||
| '@supports (-webkit-touch-callout: none)': | ||
| 'calc(var(--_progress-bar-change, 0) * var(--motion-progress-progress-view-whole-track-ms, 1000ms))', |
There was a problem hiding this comment.
On iOS a value that reverses to the one it just left runs at half this duration: CSS shortens a transition that reverses to its own start value. With the iOS gate emulated in Chromium, 10 → 90 held halfway and sent back to 10 runs 400 ms while this computes 0.8 s; a retarget to 30 keeps its 600 ms. Keep reversals off the browser's reversing path, or document that they follow the drawn distance, and add the case to ProgressBarTouch.a11y.chromium.spec.ts.
| 'pointer', | ||
| 'contrast', | ||
| 'motion', | ||
| 'platform', |
There was a problem hiding this comment.
spec:AST-012 FR2 closes the when vocabulary to width, pointer, contrast and motion: "Future environment fields require explicit additions to this closed vocabulary." platform needs that amendment first (architecture:theme-authoring-contract also says an adaptation change preserves the fixed condition vocabulary), so it would be easier to split it out with the amendment.
| * The engine is WebKit on iOS or iPadOS: the only one that implements | ||
| * `-webkit-touch-callout`. With TOUCH_QUERY, the iOS idiom. | ||
| */ | ||
| export const IOS_SUPPORTS_QUERY = '(-webkit-touch-callout: none)'; |
There was a problem hiding this comment.
spec:AST-013 FR6: "Components detect the API or CSS capability they use rather than inferring it from a browser name. A narrow engine workaround is allowed only for a reproduced defect with a regression fixture and a review or removal condition." This query is read only to tell iOS WebKit from every other engine, so the iOS idiom built on it needs FR6's owner to rule before it ships.
| measures (0.55 for a scroll view and a sheet), and a release MUST pick its | ||
| rest point by projecting its velocity the way its UIKit counterpart does: | ||
| with a deceleration rate, or with a measured projection time. | ||
| - **FR4 — Physics are not themable in touch presentations.** A touch |
There was a problem hiding this comment.
FR4, and DEC-1's "Rejected: tuning touch motion per theme", say touch motion is not themable, but this pull request adds defineTheme({motion: {springs, durations, curves}}) and ProgressBar and Spinner read --motion-* role variables a theme can set. Bring FR4 and DEC-1 in line with the roles, or drop the roles, before owners rule on this record.
| content: [ | ||
| { | ||
| type: 'prose', | ||
| text: 'On a touch screen, sheets, alerts, menus, navigation and scrolling play the springs, durations and curves measured from iOS. Each value is a motion role named by its table path, such as `springs.sheetPresent` or `timing.curves.easeInOut`. A theme retunes a role through `motion.springs`, `motion.durations` or `motion.curves`, at its root or in an adaptation rule; a role it leaves alone keeps its measured default.', |
There was a problem hiding this comment.
This says sheets, alerts, menus, navigation and scrolling play these roles, but at this head only four roles have a consumer (the three progress.* durations in ProgressBar and progress.activityIndicator.cycleMs in Spinner), so the springs.sheetPresent example below changes nothing. Document only roles a component reads, and reject role ids no table registers instead of accepting them silently.
There was a problem hiding this comment.
Approval waits on an engineering owner's decision on draft spec:AST-094 for the iOS indeterminate bar, including its iOS engine gate, which spec:AST-013 FR6 does not allow today, and its theme-retunable timing, which the draft's own FR4 rules out. An engineering owner records that decision by commenting /approve-spec b7432c040517a75d25a5827fb880aecf97c2425b on this pull request, and the author may do so as an engineering owner.
- Restore the indeterminate track, which the
nulldefault atProgressBar.tsx:281-290removes with a mouse and on Android too. - Rebase onto the stacked predecessors so this pull request carries only its own commits, drop
when.platformor amendspec:AST-012FR2, fix the halved iOS reversal, and keep the motion docs to roles that have a reader.
[Automated review]
| // with Reduce Motion the track's own color steps aside for the pulse. | ||
| trackIndeterminate: { | ||
| backgroundColor: { | ||
| default: null, |
There was a problem hiding this comment.
This null default replaces the track's --color-background-muted on every indeterminate bar, so the track renders transparent with a mouse, on Android and on iOS without Reduce Motion (the base paints rgb(212, 212, 212)). Give both defaults the track colour and keep transparent only under the gated Reduce Motion branch, then assert the track colour on the mouse and Android paths.
| default: durationVars['--duration-medium'], | ||
| '@media (pointer: coarse)': { | ||
| '@supports (-webkit-touch-callout: none)': | ||
| 'calc(var(--_progress-bar-change, 0) * var(--motion-progress-progress-view-whole-track-ms, 1000ms))', |
There was a problem hiding this comment.
A move reversed halfway (10 to 90, back to 10 when drawn at 50%) is specified as 0.8 s here, but CSS Transitions shortens a transition that reverses to its start, so Chromium runs it in 400 ms, half what progressView.wholeTrackMs documents. Start the reversal as a fresh transition or restate the table, and add the case to the touch spec.
| * is everything else. Pair `ios` with `pointer: 'coarse'` for a touch | ||
| * profile, such as touch motion tuned per platform. | ||
| */ | ||
| platform?: 'ios' | 'other'; |
There was a problem hiding this comment.
spec:AST-012 FR2 closes the when vocabulary to width, pointer, contrast and motion and says future fields need explicit additions to it; no theme in the repository sets platform, only the motion doc's example. Remove it, or amend AST-012 FR2 in its own pull request.
| * The engine is WebKit on iOS or iPadOS: the only one that implements | ||
| * `-webkit-touch-callout`. With TOUCH_QUERY, the iOS idiom. | ||
| */ | ||
| export const IOS_SUPPORTS_QUERY = '(-webkit-touch-callout: none)'; |
There was a problem hiding this comment.
This query identifies the engine to choose a presentation; spec:AST-013 FR6 allows an engine test only as a workaround for a reproduced defect with a regression fixture and a removal condition, so it needs an owner decision: amend FR6, or key the presentation on a capability.
| content: [ | ||
| { | ||
| type: 'prose', | ||
| text: 'On a touch screen, sheets, alerts, menus, navigation and scrolling play the springs, durations and curves measured from iOS. Each value is a motion role named by its table path, such as `springs.sheetPresent` or `timing.curves.easeInOut`. A theme retunes a role through `motion.springs`, `motion.durations` or `motion.curves`, at its root or in an adaptation rule; a role it leaves alone keeps its measured default.', |
There was a problem hiding this comment.
At this head only the three ProgressBar durations and Spinner's cycle read a motion role, so sheets, alerts, menus, navigation and scrolling do not follow these roles yet, and the example's springs.sheetPresent and springs.navigationPush change nothing. Document only roles that have a reader, or mark the rest as not yet in use.
There was a problem hiding this comment.
Approval waits on an engineering owner's decision about draft AST-094. That decision covers the iOS-only engine gate (current AST-013 FR6 does not allow it), the trackless Reduce Motion pulse, and theme-retunable touch roles, which AST-094 FR4 and DEC-1 forbid as written. An engineering owner records it by commenting /approve-spec <full head SHA> on this pull request, and you may do so yourself if you are an engineering owner. Whatever the decision, these still need changes:
- With Reduce Motion on iOS the pulsing bar reaches only 1.52:1 to 2.42:1 against the page, below the 3:1 non-text contrast AST-020 requires.
- Rebase onto the stacked predecessors so the pull request carries only its own four commits, about 640 of its 19,658 authored lines.
- Drop
when.platformor amend AST-012 FR2 first, derive the Spinner spoke delays from the cycle role, keep a reversed iOS move at its documented duration, document only motion roles something reads, and fix the citation of the missing AST-090.
[Automated review]
| '@media (pointer: coarse)': { | ||
| '@supports (-webkit-touch-callout: none)': { | ||
| default: colorVars['--color-background-muted'], | ||
| '@media (prefers-reduced-motion: reduce)': 'transparent', |
There was a problem hiding this comment.
With Reduce Motion on iOS this removes the track, so the pulsing fill (accent at 0.3 to 0.6 opacity) reaches only 1.52:1 to 2.42:1 against the light page, below the 3:1 non-text contrast that AST-020 requires for a meaningful state graphic. Pulsing at 0.74 opacity or more, or keeping the track and the 3 s slide that a mouse and Android keep, would hold 3:1.
| default: durationVars['--duration-medium'], | ||
| '@media (pointer: coarse)': { | ||
| '@supports (-webkit-touch-callout: none)': | ||
| 'calc(var(--_progress-bar-change, 0) * var(--motion-progress-progress-view-whole-track-ms, 1000ms))', |
There was a problem hiding this comment.
A move sent back to its starting value runs at half this duration, because CSS shortens a transition that reverses to its start. With the iOS gate emulated, 10 → 90 takes 800 ms; sent back to 10 halfway, it takes 400 ms, while 90 → 30 from the same point takes the documented 600 ms. A reversal test, plus a path the reversing rule does not shorten (or a documented exception in progress.ts), would close this.
| // spoke partway through its cycle, so every spoke is in step from the first | ||
| // frame. | ||
| const spokeStyles = stylex.create({ | ||
| s0: {animationDelay: '-600ms'}, |
There was a problem hiding this comment.
These delays are sized for the 800 ms default, but the duration reads --motion-progress-activity-indicator-cycle-ms, which themes are invited to retune. At 1600 ms the bright head sits on one spoke for 800 ms and then steps 0, 7, 0, 1…. Deriving each delay from the same variable, for example calc(var(--motion-progress-activity-indicator-cycle-ms, 800ms) * -6 / 8) here, keeps the order at any cycle.
| * is everything else. Pair `ios` with `pointer: 'coarse'` for a touch | ||
| * profile, such as touch motion tuned per platform. | ||
| */ | ||
| platform?: 'ios' | 'other'; |
There was a problem hiding this comment.
AST-012 FR2 closes the condition vocabulary to width, pointer, contrast and motion, and says future environment fields need an explicit addition to it. This adds platform without amending FR2, and nothing in this pull request reads it. Dropping it here, or amending AST-012 first, resolves the contradiction.
| * The engine is WebKit on iOS or iPadOS: the only one that implements | ||
| * `-webkit-touch-callout`. With TOUCH_QUERY, the iOS idiom. | ||
| */ | ||
| export const IOS_SUPPORTS_QUERY = '(-webkit-touch-callout: none)'; |
There was a problem hiding this comment.
AST-013 FR6 asks components to detect the capability they use rather than infer the engine, and allows an engine workaround only for a reproduced defect with a regression fixture and a removal condition. Here the property identifies iOS WebKit in order to choose a presentation, so it needs an engineering owner's decision (AST-094 approved together with an FR6 amendment) before it can land.
| ); | ||
| } | ||
| const registered = motionRole(id); | ||
| if (registered !== undefined && registered.kind !== kind) { |
There was a problem hiding this comment.
A well-formed id that names no registered role passes this check and writes a variable nothing reads; the error text even suggests sheet.present, which no table registers. Rejecting unregistered ids would catch a theme author's typo.
| content: [ | ||
| { | ||
| type: 'prose', | ||
| text: 'On a touch screen, sheets, alerts, menus, navigation and scrolling play the springs, durations and curves measured from iOS. Each value is a motion role named by its table path, such as `springs.sheetPresent` or `timing.curves.easeInOut`. A theme retunes a role through `motion.springs`, `motion.durations` or `motion.curves`, at its root or in an adaptation rule; a role it leaves alone keeps its measured default.', |
There was a problem hiding this comment.
Only four of the 50 roles have a reader in this pull request: three in ProgressBar and one in Spinner. Sheets, alerts, menus, navigation and scrolling do not play these roles yet, so this sentence and the example's springs.sheetPresent and springs.navigationPush promise effects a theme will not see.
…ers change at once (#8272) ## Exact decision needed What a person who prefers reduced motion sees when a core component animates a change, on touch and on other pointers, and whether a component may keep its motion. ## Existing authority searched - `design:motion` DR5 (draft): every animated transition has an immediate or minimally moving form. Draft, so not authority. - `spec:AST-013` FR13: reduced-motion behavior is unchanged by server-rendered entrances; it defines none. - `spec:AST-012` FR2: `motion: reduce` and `pointer: coarse/fine` are the adaptation conditions. - No current web record defines component behavior under reduced motion. - Search terms: reduced motion, prefers-reduced-motion, cross-fade, Reduce Motion, motion DR5, spring, travel. Overlapping open pull requests: #7367 (draft `design:motion` DEC-2, hover entrances; no reduced-motion rule), #8214 (layer fade under a finger with Reduce Motion; conforms once its fade is 200 ms or less), #8052 (AST-094 FR8 carries the matching touch rule), #8211, #8239, #8253 (touch indicator and placeholder work; continuous indicators are outside this record). ## Decision New current record `design:reduced-motion`, matching iOS Reduce Motion with Prefer Cross-Fade: - DR1: under `prefers-reduced-motion: reduce`, a core component's entrances, exits, presentation switches, and state changes never move, scale, resize, or spring. - DR2: on a coarse pointer, they cross-fade: opacity only, 200 ms or less. - DR3: on any other pointer, they complete at once. - DR4: no core component, prop, or theme value keeps motion under reduced motion. `spec:AST-057` FR5 names that a swiped row's slide out, spring back, and hold-out follow DR2 under reduced motion. The draft `design:motion` keeps its other rules unapproved; its DR5 points to the new record. A separate record keeps the promotion to this one rule, as `docs/design/README.md` advises for a different approval lifecycle. ## Rejected alternatives - A per-component setting that keeps full motion under reduced motion. ## Claim scope and non-goals Only transitions under reduced motion. Motion without the preference, durations and easing, tokens, continuous indicators such as a spinner, and content the person drags with their own finger are outside this record. ## Evidence and implementation relationship Layers that open under a finger with Reduce Motion appear in one frame today, where iOS cross-fades. The open fix #8214 makes them fade under a coarse pointer and stay immediate under a mouse, which conforms to DR2 and DR3 once its fade is 200 ms or less. The open draft #7367 (`design:motion` DEC-2, hover-summoned layers) does not touch reduced motion. The AST-094 revision in #8052 carries the matching touch rule. No implementation is included. ## Writing rubric results - R1 narration: none. - R2 research in the record: none. - R3 evidence naming a change: none; verification names browser states. - R4 rejected alternatives over one line or outside a decision: one, inside DEC-1, one line. - R5 pull request or issue references remaining: none. - R6 ruled open questions still listing options: none. ## Scope - [x] This PR contains specification records only. - [x] It records observable behavior and preserves equivalent internal implementations. - [x] Any named internal mechanism is intentionally public, names its dependent caller/system, and explains why an equivalent implementation would not satisfy the contract. - [x] Verification states the evidence layer and failure signal without prescribing CI job/workflow topology. - [x] It records one intentional durable decision, not a rescue for a separable tagalong. - [x] The canonical knowledge owner, affected current claims, and overlapping open work are named; any ownership collision is resolved or called out for human decision. - [x] Adjacent API, visual, accessibility, composition, and implementation facts remain outside scope unless this exact decision depends on them. - [x] Public text and artifacts contain no internal Meta context. ## Validation - `pnpm check:knowledge` (CI lint) - Prettier - `git diff --check`
|
Posted by Cindy's agent on her behalf. Parts of this PR re-implement behavior that already exists, or is being specified, as a shared part of core. Converging keeps one implementation per behavior:
This is about where the behavior lives, not about the PR's intent. |
|
Agreed: one implementation.
|
There was a problem hiding this comment.
Approval needs an engineering owner to approve or identify the governing spec for the iOS Safari look of ProgressBar's indeterminate bar, which today rests only on draft spec:AST-094: a 25% linear sweep, a Reduce Motion pulse with no track, whether it applies on iOS only or on every touch screen, and whether themes may retune its timing. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Whatever that decision, please rebase onto the landed stack so this diff is the ProgressBar change alone, and fix these:
- Use the shared compact definition from
spec:AST-012FR9 instead of 744 px, which opens adaptive menus as popovers on 744–767 px touch screens. - Key the touch styles on the shared touch resolver instead of
-webkit-touch-callout, asspec:AST-013FR6 and the owner comment ask. - Drop
when.platform, the theme-settable reduced-motion fade andreducedMotion: 'keep', whichspec:AST-012FR2 and the reduced-motion design record rule out. - Make the motion docs describe only roles a component reads, reject unknown role ids, and fix AST-094's citation of the missing AST-090.
[Automated review]
| export const TOUCH_QUERY = '(pointer: coarse)'; | ||
|
|
||
| /** The viewport is compact: layout behaviours. */ | ||
| export const COMPACT_QUERY = '(width < 744px), (height < 500px)'; |
There was a problem hiding this comment.
spec:AST-012 FR9 makes a screen compact below the effective md point (768 px by default) and says no resolver keeps size numbers of its own; on a 750 px touch screen this opens adaptive menus as popovers. main exports compactDefinition() / DEFAULT_COMPACT_DEFINITION for this.
| * The engine is WebKit on iOS or iPadOS: the only one that implements | ||
| * `-webkit-touch-callout`. With TOUCH_QUERY, the iOS idiom. | ||
| */ | ||
| export const IOS_SUPPORTS_QUERY = '(-webkit-touch-callout: none)'; |
There was a problem hiding this comment.
-webkit-touch-callout stands in for "iOS" here, which spec:AST-013 FR6 allows only for a reproduced engine defect with a fixture and a removal condition; the engineering owner asked for the shared touch key instead.
| * is everything else. Pair `ios` with `pointer: 'coarse'` for a touch | ||
| * profile, such as touch motion tuned per platform. | ||
| */ | ||
| platform?: 'ios' | 'other'; |
There was a problem hiding this comment.
platform is not in spec:AST-012 FR2's closed when vocabulary (width, height, pointer, contrast, motion), which requires an explicit addition to the record first.
| * Cross-Fade Transitions on (opacity CABasicAnimation 0.5 s, | ||
| * (0.42, 0, 0.58, 1)), Oct 23 2026 | ||
| */ | ||
| reducedMotionCrossFadeMs: 500, |
There was a problem hiding this comment.
As a theme role this lets a theme lengthen the reduced-motion fade, and its 500 ms default exceeds the 200 ms design:reduced-motion DR2 allows on touch; DR4 says theme values offer no way to keep a longer animation under reduced motion.
| `${location}: a motion role is named by its table path, such as "sheet.present" or "springs.navigationPush".`, | ||
| ); | ||
| } | ||
| const registered = motionRole(id); |
There was a problem hiding this comment.
An id the registry does not know passes through and emits a variable nothing reads (the test expects 'sheet.present' to become --motion-sheet-present), so a typo in a theme fails silently.
| content: [ | ||
| { | ||
| type: 'prose', | ||
| text: 'On a touch screen, sheets, alerts, menus, navigation and scrolling play the springs, durations and curves measured from iOS. Each value is a motion role named by its table path, such as `springs.sheetPresent` or `timing.curves.easeInOut`. A theme retunes a role through `motion.springs`, `motion.durations` or `motion.curves`, at its root or in an adaptation rule; a role it leaves alone keeps its measured default.', |
There was a problem hiding this comment.
At this head only ProgressBar and Spinner read a --motion-* variable and no component calls the animator, so retuning springs.sheetPresent or springs.navigationPush as the example shows changes nothing.
| 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.
spec:AST-090 is not on main or in this pull request, so this amendment cannot be checked.
There was a problem hiding this comment.
Approval is on hold until an engineering owner settles the governing spec for ProgressBar's touch presentation, which no current record states: the 25% linear sweep, the trackless 0.74–1 Reduce Motion pulse and its departure from Safari's measured 0.3–0.6 (spec:AST-094 leaves each component's presentation to its own record, and component:ProgressBar is still a draft). An engineering owner records that decision by commenting /approve-spec 64f26c11fb5cfc53c09a38dd751b0fccdfff0de1 on this pull request, and the author may do so if they are an engineering owner. Whatever the decision, these changes are still needed:
- Rebase onto
mainonce #8076, #8177, #8211, #8196 and #8225 land, so the diff (23,602 authored lines today) holds only this branch's commits. - Read the sweep, pulse and fill timing through motion roles, since
spec:AST-094FR4 says a component “MUST NOT play a literal”. - Mirror the sweep on
:dir(rtl), as the determinate fill already does, so the segment stays on the track in a left-to-right region of a right-to-left page. - Keep the Spinner ring (it contradicts
component:SpinnerFR3) and the gesture host (it contradictsspec:AST-094FR18 and FR20) in their own pull requests, and add real-device iOS Safari and Android Chrome recordings for the touch claims.
[Automated review]
| export const progressBarTouchConsts = stylex.defineConsts({ | ||
| /** | ||
| * UIProgressView's setProgress(_:animated: true) duration for a change of | ||
| * the whole track; a smaller change takes its share of it. | ||
| */ | ||
| wholeTrack: '1000ms', | ||
| /** iOS Safari's indeterminate segment: one sweep of the track. */ | ||
| sweep: '1000ms', | ||
| /** Under Reduce Motion, each direction of Safari's pulse instead. */ | ||
| reducedMotionPulse: '1000ms', | ||
| /** The curve of all three: linear. */ | ||
| easing: 'linear', | ||
| }); |
There was a problem hiding this comment.
These constants are literals played in place of motion roles. spec:AST-094 FR4 says every duration and curve a touch presentation plays “MUST be a motion role named for its use” and that a component “MUST NOT play a literal”. Naming them as roles in ProgressBar's record, so they join the generated role table (DEC-5), and reading them through the roles would conform; otherwise FR4 needs amending.
| '@media (pointer: coarse)': { | ||
| default: touchSweep, | ||
| ':is([dir="rtl"] *)': touchSweepRtl, | ||
| }, |
There was a problem hiding this comment.
This mirrors on any RTL ancestor, while the segment sits at its own region's start. In a left-to-right region of a right-to-left page the sweep starts at the left end and moves left, so the segment is off the track from about 333 ms to each turn. Keying the mirror on :dir(rtl), as the determinate fill does at line 342, keeps it on the track.
| <span | ||
| ref={syncRotationPhase} | ||
| aria-hidden="true" | ||
| {...stylex.props(styles.rotor)}> |
There was a problem hiding this comment.
The rotating span around the arc's own <svg> no longer matches component:Spinner FR3 (“an aria-hidden SVG of two concentric circles … The arc's dash moves around the stationary circle”). This belongs in its own pull request that conforms to FR3 or amends it.
| } | ||
| } | ||
|
|
||
| private applyStyle(element: HTMLElement, registration: Registration): void { |
There was a problem hiding this comment.
applyStyle writes the derived touch-action over the element's own value and saves that only to restore it later. spec:AST-094 FR18 requires the derived value to be intersected with the author's touch-action and never be wider than it.
| }); | ||
| } | ||
|
|
||
| private readonly onScroll = (event: Event): void => { |
There was a problem hiding this comment.
Any scroll event hands every unclaimed touch inside the scroller to the browser, with no travel check. spec:AST-094 FR20 counts a native scroll only once the finger has moved the 10 px scroll take distance, so sub-pixel drift under a still finger does not cancel a tap.
64f26c1 to
1907505
Compare
|
Rebuilt on the new #8225 head. The Spinner ring is #8177's change and is no longer in this branch, and its own change is one commit: vjeux/astryx@d8e4dab...1907505 |
There was a problem hiding this comment.
Approval is on hold until an engineering owner settles the governing spec for ProgressBar's touch presentation, which no current record states: the 25% linear sweep, the trackless 0.74–1 Reduce Motion pulse and its departure from Safari's measured 0.3–0.6 (spec:AST-094 leaves each component's presentation to its own record, and component:ProgressBar is still a draft). An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Whatever the decision, these changes are still needed:
- Rebase onto
mainonce #8076, #8196 and #8225 land, so the diff (21,897 authored lines today) holds only this branch's commits. - Mirror the touch sweep on
:dir(rtl), as the determinate fill already does, so the segment stays on the track in a left-to-right region of a right-to-left page. - Read the sweep, pulse and fill timing through motion roles, since
spec:AST-094FR4 says a component “MUST NOT play a literal”. - In the carried code, stop the forced-colors fill animating
widthunder a finger (FR5), bring the gesture host to FR18 and FR20, and add real-device iOS Safari and Android Chrome recordings for the touch claims.
[Automated review]
| }, | ||
| transitionProperty: { | ||
| default: 'transform', | ||
| '@media (forced-colors: active)': 'width', |
There was a problem hiding this comment.
Under forced colors with a coarse pointer this switches the fill's transition to width, while the touch key still gives it calc(var(--_progress-bar-change, 0) * 1000ms), linear (lines 387-398). On a touch screen with a contrast theme, a value change then lays out every frame and stalls with the main thread. spec:AST-094 FR5 says a touch presentation's animation “MUST NOT animate layout properties”. Landing the value at once under forced colors (transitionDuration: '0s' there) would conform; this arrives with #8196's fix, so it belongs there.
There was a problem hiding this comment.
Approval is on hold until an engineering owner settles the governing spec for ProgressBar's touch indeterminate bar, which no current record states: Safari's 25% linear sweep, the trackless 0.74–1 Reduce Motion pulse (Safari measures 0.30–0.60), and whether its timing must be AST-094 FR4 motion roles rather than StyleX constants. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, or by naming the record that should state it, and the author may do so as an engineering owner. Either way, please:
- Rebase or retarget onto #8225 so this pull request carries its own 699 changed lines instead of 19,100.
- Key the touch sweep's RTL keyframes on
:dir(rtl), becauseProgressBar.tsx:438runs the segment off the track in a left-to-right region of a right-to-left page. - Add real-device recordings for the iOS Safari and Android Chrome claims, as AST-094's platform support section requires.
[Automated review]
| ':is([dir="rtl"] *)': indeterminateSlideRtl, | ||
| '@media (pointer: coarse)': { | ||
| default: touchSweep, | ||
| ':is([dir="rtl"] *)': touchSweepRtl, |
There was a problem hiding this comment.
This key matches any RTL ancestor, so a bar in a left-to-right region of a right-to-left page plays the right-to-left sweep from the left edge and leaves the track: 89.5, 55.9, 22.4 and 0 px stay visible at 0, 125, 250 and 500 ms, and the bar is empty from 333 to 1,667 ms of each cycle. Keying it on :dir(rtl), as the determinate fill does at line 356, follows the bar's own direction (design:bidirectionality DR3).
| export const progressBarTouchConsts = stylex.defineConsts({ | ||
| /** | ||
| * UIProgressView's setProgress(_:animated: true) duration for a change of | ||
| * the whole track; a smaller change takes its share of it. | ||
| */ | ||
| wholeTrack: '1000ms', | ||
| /** iOS Safari's indeterminate segment: one sweep of the track. */ | ||
| sweep: '1000ms', | ||
| /** Under Reduce Motion, each direction of Safari's pulse instead. */ | ||
| reducedMotionPulse: '1000ms', | ||
| /** The curve of all three: linear. */ | ||
| easing: 'linear', | ||
| }); |
There was a problem hiding this comment.
spec:AST-094 FR4 says every duration and curve a touch presentation plays MUST be a motion role named for its use, and a component MUST NOT play a literal. These four values are literals, and the progress table registers no role (motion/uikit/progressBar.ts:121-123); registering them as named roles of that table and reading them through the role path would conform, unless an engineering owner exempts them.
There was a problem hiding this comment.
Approval is on hold until an engineering owner settles the governing spec for ProgressBar's indeterminate bar under a finger: no current record states Safari's 25% linear sweep, the 0.74 to 1.0 Reduce Motion pulse with no track, or whether spec:AST-094 FR4 needs motion roles for their timing. An engineering owner records that decision by commenting /approve-spec 2ea5d4697c0f4ec635fba15af6ca4709215e4931 on this pull request, and the author may do so as an engineering owner. Whatever that decision, please key the touch sweep on :dir(rtl) (ProgressBar.tsx:438), because a left-to-right bar inside a right-to-left page is empty for two thirds of each cycle, land #8225 and its parents first so this diff is its own 699 lines, and attach real iOS Safari and Android Chrome recordings.
[Automated review]
| ':is([dir="rtl"] *)': indeterminateSlideRtl, | ||
| '@media (pointer: coarse)': { | ||
| default: touchSweep, | ||
| ':is([dir="rtl"] *)': touchSweepRtl, |
There was a problem hiding this comment.
This key matches any right-to-left ancestor, while the 25% segment sits at the start of its own region, so a left-to-right bar inside a right-to-left page sweeps left off the track. On a 370 px track, 92.5, 57.8, 23.1 and 0 px are visible at 0, 125, 250 and 500 ms, and the bar is empty from 333 to 1,667 ms of each 2 s cycle. design:bidirectionality DR3 asks ordered progression to follow the containing region's direction. Keying the sweep on ':dir(rtl)', as the determinate fill does at lines 356 and 369, fixes it; a left-to-right-inside-right-to-left test like the one in ProgressBarMotion.a11y.chromium.spec.ts would keep it fixed.
| sweep: '1000ms', | ||
| /** Under Reduce Motion, each direction of Safari's pulse instead. */ | ||
| reducedMotionPulse: '1000ms', | ||
| /** The curve of all three: linear. */ | ||
| easing: 'linear', |
There was a problem hiding this comment.
These are literals played in place of motion roles. spec:AST-094 FR4 says every duration and curve a touch presentation plays MUST be a motion role named for its use and that a component MUST NOT play a literal, and registerProgressBarTable() registers progress with no roles. Unless an engineering owner rules otherwise on the open question, name the sweep, the pulse and the linear curve as roles in the generated table (DEC-5) and read them here.
There was a problem hiding this comment.
Approval is on hold until an engineering owner settles the governing spec for ProgressBar's indeterminate bar under a finger: no current record states Safari's 25% linear sweep, the trackless 0.74 to 1.0 Reduce Motion pulse, or whether spec:AST-094 FR4 needs motion roles for their timing. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Whatever that decision, these changes are still needed:
- Land #8225 and its parents first, or retarget onto #8225, so this diff is its own 699 lines rather than 18,847.
- Key the touch sweep on
:dir(rtl)(ProgressBar.tsx:438), because a left-to-right bar inside a right-to-left page is empty for two thirds of each cycle. - Attach real-device iOS Safari and Android Chrome recordings for the touch claims.
[Automated review]
| ':is([dir="rtl"] *)': indeterminateSlideRtl, | ||
| '@media (pointer: coarse)': { | ||
| default: touchSweep, | ||
| ':is([dir="rtl"] *)': touchSweepRtl, |
There was a problem hiding this comment.
This key matches any right-to-left ancestor, while the 25% segment sits at the start of its own region, so a left-to-right bar inside a right-to-left page sweeps left off the track: on a 370 px track 92.5, 57.8, 23.1 and 0 px are visible at 0, 125, 250 and 500 ms, and the bar is empty from 333 to 1,667 ms of each 2 s cycle. design:bidirectionality DR3 asks ordered progression to follow the containing region's direction. Keying the sweep on ':dir(rtl)', as the determinate fill does at lines 356 and 369, fixes it; a left-to-right-inside-right-to-left case like the one in ProgressBarMotion.a11y.chromium.spec.ts would keep it fixed.
| sweep: '1000ms', | ||
| /** Under Reduce Motion, each direction of Safari's pulse instead. */ | ||
| reducedMotionPulse: '1000ms', | ||
| /** The curve of all three: linear. */ | ||
| easing: 'linear', |
There was a problem hiding this comment.
These are literals played in place of motion roles. spec:AST-094 FR4 says every duration and curve a touch presentation plays MUST be a motion role named for its use and that a component MUST NOT play a literal, and registerProgressBarTable() registers progress with no roles. Unless an engineering owner rules otherwise on the open question, name the sweep, the pulse and the linear curve as roles in the generated table (DEC-5) and read them here.
There was a problem hiding this comment.
Approval is on hold until an engineering owner settles the governing spec for ProgressBar's indeterminate bar under a finger: no current record states Safari's 25% linear sweep or the trackless 0.74 to 1.0 Reduce Motion pulse (spec:AST-094 leaves each component's touch presentation to its own record, and the ProgressBar record is still a draft), and the owners also need to say whether AST-094 FR4's motion roles apply to that timing. An engineering owner can record the decision by commenting /approve-spec <full head SHA> on this pull request (the author may do so as an engineering owner), name the record that should state it, or keep today's slide on touch. Whatever that decision, these changes are needed:
- Land #8225 and its parents first, or retarget onto #8225, so this diff is its own 702 lines rather than 17,109.
- Key the touch sweep on
:dir(rtl)instead of:is([dir="rtl"] *)(ProgressBar.tsx:438), because a left-to-right bar inside a right-to-left page is empty for two thirds of every cycle. - Attach recordings from a real iPhone in Safari and a real Android phone in Chrome for the touch claims, as AST-094's platform support requires.
[Automated review]
| ':is([dir="rtl"] *)': indeterminateSlideRtl, | ||
| '@media (pointer: coarse)': { | ||
| default: touchSweep, | ||
| ':is([dir="rtl"] *)': touchSweepRtl, |
There was a problem hiding this comment.
This key matches any right-to-left ancestor, while the 25% segment sits at the start of its own region, so a left-to-right bar inside a right-to-left page plays the right-to-left sweep from the left end and leaves the track: on a 370 px track 92.5, 57.8, 23.1 and 0 px of it are visible at 0, 125, 250 and 500 ms, and the track is empty from 333 to 1,667 ms of every 2 s cycle. design:bidirectionality DR3 asks ordered progression to follow the containing region's direction. Keying the sweep on ':dir(rtl)', as the determinate fill does at lines 356 and 369, fixes it; a left-to-right-inside-right-to-left case in ProgressBarTouch.a11y.chromium.spec.ts would keep it fixed.
Under a coarse primary pointer a determinate value change moves the way UIProgressView's setProgress(_:animated: true) does: linearly, for the progress.fillTrackMs role (1000 ms per whole track) times the share of the track the value moved, on the progress.curves.fill role, scaled by the theme's tempo, starting from wherever the fill is drawn. Above zero the fill draws at least two track heights (16px), a mark that length covers counts as on the fill, and at zero it paints nothing. A mouse keeps the theme's timing, and reduced motion still lands at once (component:ProgressBar FR4).
9f35484 to
c234eaf
Compare
…s at once Under forced colors the fill is sized by width, a layout property that touch motion never animates (spec:AST-094 FR5), so with a finger it now lands at once there instead of taking the touch duration. The touch layer no longer registers the progress table in a mount Effect: nothing at runtime reads the registry, and the generated role table registers it.
There was a problem hiding this comment.
Changes requested: the touch timing and the reduced-motion pulse match the ProgressBar record and their tests pass, but three small fixes are needed first.
- With forced colors under a coarse pointer, the new timing drives the fill's
widthtransition (800 ms, linear), which AST-094 FR5 rules out; land the sized fill at once there and add a forced-colors test. - Remove the dependency-free
useEffectthat callsregisterProgressBarTable()inProgressBarTouch.ts, since nothing reads the registry at runtime. - Scope the reduced-motion changeset's 3:1 promise to the built-in tokens: in the Neutral, Butter and Matcha themes, some tones fall below 3:1 at the 0.84 floor.
[Automated review]
| default: durationVars['--duration-medium'], | ||
| // The touch layer's move (the `progress.fillTrackMs` role times the | ||
| // share moved), scaled by the theme's tempo (`spec:AST-108` FR3). | ||
| '@media (pointer: coarse)': |
There was a problem hiding this comment.
Under forced colors this fill transitions width (line 333), and this coarse key has no forced-colors exception, so on a touch screen with a contrast theme a value change animates layout for 800 ms, linearly (10% → 90%), instead of landing. AST-094 FR5 says a touch presentation's animation MUST NOT animate layout properties. Nesting '@media (forced-colors: active)': '0s' under this key would land the sized fill at once there; a forced-colors case asserting no running width transition would keep it fixed.
| const watchesTrack = isTouch && hasMarks; | ||
| const trackRef = useRef<HTMLDivElement | null>(null); | ||
| const [trackWidth, setTrackWidth] = useState(0); | ||
| useEffect(() => { |
There was a problem hiding this comment.
This Effect names no external system and depends on nothing (react-component-runtime INV3), and nothing in ProgressBar reads the role registry at runtime: resolveMotion returns the value it is handed, and the role-table generator and roles tests register every table themselves. Deleting it (and the note in motion/uikit/progressBar.ts that the touch layer calls it) keeps behaviour the same; if a runtime entry is wanted, register at the read site as motion/coast.ts does.
| '@astryxdesign/core': patch | ||
| --- | ||
|
|
||
| [feat] Under reduced motion, an indeterminate ProgressBar no longer slides: on every pointer its fill spans the whole track and gently pulses its opacity in place, from full down to 84% and back, two seconds each way. A theme's tempo does not change the pulse, and every fill tone that stands out from the track at 3:1 still does at the pulse's lowest point. |
There was a problem hiding this comment.
The 3:1 promise holds for the built-in light and dark tokens (AR4 and the pulse-floor test), but not in every theme: at the 0.84 floor, Neutral light success goes from 3.39:1 to 2.79:1, Butter dark success from 3.12 to 2.64, Matcha light success from 3.11 to 2.56 and Matcha dark error from 3.23 to 2.66. Scoping it, for example to "in the built-in light and dark tokens", or dropping the clause, would keep the release note accurate.
…in place Under reduced motion, on every pointer, the indeterminate fill spans the whole track as one solid bar and only its opacity pulses, from 1 down to 0.84 and back, each way taking the progress.reducedMotionPulseMs role (2000 ms) on the timing.curves.move curve; tempo does not scale it. The 0.84 floor keeps every tone that reaches 3:1 against the track at full opacity at 3:1 in the light and dark default themes (component:ProgressBar FR6, AR4).
c234eaf to
396791a
Compare
There was a problem hiding this comment.
Ready to approve: the touch timing and the reduced-motion pulse match the ProgressBar record, and the forced-colors fill, the extra Effect and the changeset wording from the last review are all fixed. The new Chromium specs and the unit tests pass. The inline notes are optional comment cleanups (a FillStyle comment that drifted onto PulseStyle, one long comment and one long file header).
[Automated review]
| // inline style — which publishes the filled share as the private | ||
| // `--_progress-bar-fill` — needs this augmentation to typecheck. | ||
| type FillStyle = CSSProperties & Record<'--_progress-bar-fill', string>; | ||
| // The reduced-motion pulse's variables, from the progress table (FR6). |
There was a problem hiding this comment.
Optional: the three comment lines just above (517-519) explain FillStyle, but this new type now sits between them and type FillStyle. Move them down to sit directly above type FillStyle.
| // `--_progress-bar-flip` steps 0, 1, 2, 0… on every change and moves | ||
| // the fill by that many hundredths of a pixel, so a change back to | ||
| // the value it just left is never the running transition's own start | ||
| // value: CSS shortens a transition that reverses to its start, which | ||
| // would halve a return mid-flight (10 → 90 → 10). It takes three | ||
| // values because two would repeat on that return. |
There was a problem hiding this comment.
Optional: this states a real quirk, but the first sentence runs to about 55 words. A shorter form that keeps only the reason:
// CSS shortens a transition that reverses to its own start value, so a
// return mid-flight (10 → 90 → 10) would run at half length.
// `--_progress-bar-flip` steps 0, 1, 2 on each change and shifts the fill by
// that many hundredths of a pixel. Two steps would repeat on a return.| /** | ||
| * @file ProgressBarTouch.ts | ||
| * @input Uses React (useEffect, useRef, useState), the touch presentation | ||
| * resolver (useTouchPresentation), the shared resize observer, and the | ||
| * progress table's roles (motion/uikit progressBar, read through | ||
| * resolveMotion) | ||
| * @output useProgressBarTouch: what the touch presentation needs from script, | ||
| * the length of the move a change makes (`--_progress-bar-move`: the | ||
| * `progress.fillTrackMs` role times the share of the track it moved), its | ||
| * curve (`--_progress-bar-curve`: the `progress.curves.fill` role), the | ||
| * three-step offset that keeps a return off CSS's reversing path | ||
| * (`--_progress-bar-flip`), and, under a finger with marks, the share of | ||
| * the track the fill draws at least (TOUCH_MIN_FILL_PX), measured on the | ||
| * track | ||
| * @position ProgressBar's touch layer, with one import site (ProgressBar.tsx): | ||
| * everything ProgressBar runs in script for a finger lives here | ||
| * | ||
| * SYNC: When modified, update /packages/core/src/ProgressBar/ProgressBar.tsx | ||
| * (its touch styles read these variables and the 16px minimum) and | ||
| * /packages/core/src/motion/uikit/progressBar.ts. | ||
| */ |
There was a problem hiding this comment.
Optional: this header runs to 21 lines, past the roughly 12-line limit for file headers. @output can name the hook and its three variables, with the detail left to the hook's own comments.
Stacks on #8225; only the last commit is new here.
User impact
With reduced motion on, an indeterminate ProgressBar still slides a 40% segment across the track, slowed to 3 s a pass.
design:reduced-motionDR1 says nothing should travel there, so the bar now stays put: on every pointer its fill spans the whole track and only its opacity pulses, from full down to 0.84 and back, 2 s each way.Options and tradeoffs:
Visual authority
DDrow reused or amended.Design decisionstable by a team design owner.docs/design/.Settled by
component:ProgressBarFR6, AR4 and DEC-2: the pulse plays theprogress.reducedMotionPulseMsrole ontiming.curves.move, which tempo does not scale.Before and after
Reduced motion, Chromium, one bar seeked through a cycle; before (main) then after:
State matrix
Writing rubric results
component:ProgressBar(FR6's gap lines, the FR6 and AR4 verification rows) and the generated role table: R1–R6 none; the rows name the new tests.Scope
Testing
Local gates at 396791a: ProgressBar and motion units 920/920; ProgressBar browser specs 43/43, and ×3 on two pinned cores 129/129; typecheck, lint, the changeset, knowledge and role-table checks pass.