Repository navigation
spec(AST-094): only a gesture's motion springs; other motion plays the theme's tokens - #9706
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
There was a problem hiding this comment.
Approval of this spec change waits on an engineering-owner decision: FR44 and DEC-26 in AST-094, and the matching changes to the ten component records, replace current requirements (FR1 and FR4's measured roles, and FR7's native exit, for motion no gesture drives), and no owner approval is recorded at this head yet. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so as an engineering owner.
These need fixing whatever the decision:
- FR7, AlertDialog FR6 and Lightbox FR16 now play a tap-closed touch exit on AST-090's exit tokens, but AST-090 FR14, its Non-goals and DEC-5 still say those tokens never change a touch exit, so please amend AST-090 here and make FR31's shrink conditional on a non-zero exit duration, since the default is 0 ms.
- AST-094 FR1 still requires every duration a touch presentation uses to default to the measured native value, which contradicts FR44 for the grow and the lift, so please scope FR1 to motion a gesture drives, as FR4 now is.
- FR44, FR4 and FR38 say the theme's
character"normalizes" a gesture's spring and cite AST-108 FR3, which sayscharacteroffsets each role's measured bounce by a fixed weight, so please use AST-108's wording or amend AST-108 FR3 in this pull request. - DateInput FR14 now requires the wheel to turn on the theme's move duration and easing, but the shipped wheel turns with the browser's smooth scroll (
Wheel.tsx), so please record that implementation gap in FR14 and in AST-094's gap list.
Non-blocking: FR44's requirement has no stated exception for the animated-layout and gesture-hook springs the impact section keeps; stating it in FR44 itself would keep the requirement true.
[Automated review]
| exit MUST keep the look of its UIKit counterpart's exit, on every touch | ||
| platform: a menu shrinks into its trigger, and an alert fades. An exit that | ||
| a tap, a key, an outside press or the program starts plays on the theme's | ||
| exit duration and easing tokens (`spec:AST-090`, FR44); an exit that a drag |
There was a problem hiding this comment.
This makes a tap-closed touch exit play AST-090's exit tokens, but AST-090 FR14 ("The exit switches, --ease-exit, and --duration-exit do not change that exit"), its Non-goals and DEC-5 still say the opposite and are not amended in this pull request. Please amend AST-090 here; with its 0 ms default, FR31's shrink also needs to say it plays only when a theme sets an exit duration.
| spring's parameters and its `tempo` scales the spring's duration | ||
| (`spec:AST-108` FR3, FR4). A part that follows a pointer follows it under | ||
| every pointer (FR43). Every other motion, which a tap, a click, a hold, a | ||
| key or the program starts, MUST play the theme's default motion, its |
There was a problem hiding this comment.
FR1 (unchanged) still says every spring and duration a touch presentation uses MUST default to the measured native value, and FR13 now counts the grow and the lift as touch presentation. Scoping FR1 to motion a gesture drives, as FR4 now is, removes the contradiction.
| starts at the gesture's velocity, under every pointer (FR43). A gesture is | ||
| a continuous pointer movement whose velocity carries into the motion: a | ||
| drag's release, a flick, a swipe, a pan's settle, a pinch, a rubber band's | ||
| return, and a follow and its settle. The theme's `character` normalizes the |
There was a problem hiding this comment.
AST-108 FR3 says character offsets each role's measured bounce by a fixed per-role weight and that a spring keeps its measured physics; "normalizes" reads as a different rule. Please use AST-108's wording here and in FR4 and FR38, or state the new rule and amend AST-108 FR3.
| | FR13 | A bottom-sheet picker wheel that comes to rest on a disabled row (outside `min`/`max`) MUST continue to the nearest enabled row, a tie going toward the committed row, and select it there. A wheel resting on its committed row MUST NOT select or commit, even when that row is outside the bounds, so opening a bounded sheet never changes an untouched value: a committed value outside the bounds stays, and an empty field stays empty. This holds for every wheel in the DateInput, TimeInput, and DateTimeInput sheets. | DEC-1; UIDatePicker on the iOS 26.2 simulator; current source | | ||
| | FR14 | A shown bottom-sheet picker wheel whose value changes from outside, as when a bound clamps the time after another wheel turned, MUST turn onto the new row rather than jump to it. | UIDatePicker on the iOS 26.2 simulator; current source | | ||
| | FR15 | Under a coarse primary pointer, a sheet wheel's pan takes a trackpad's two-finger scroll, as `spec:AST-094` FR43 allows a record to grant: the rows follow the fingers, coast on at the lift with the fingers' last speed, and rest on a row by FR13. A mouse-wheel notch turns the wheel one row, as an arrow key does, with no coast. A ctrl-scroll stays the page's zoom (`spec:AST-094` FR43). Implementation gap: the wheel is the browser's scroll-snap column for both inputs, so a trackpad's momentum runs at the browser's rate and a mouse-wheel notch moves the column by the browser's scroll step, several rows. | `spec:AST-094` FR43; current source | | ||
| | FR14 | A shown bottom-sheet picker wheel whose value changes from outside, as when a bound clamps the time after another wheel turned, MUST turn onto the new row rather than jump to it, on the theme's move duration and easing tokens (`spec:AST-094` FR44). | UIDatePicker on the iOS 26.2 simulator; current source | |
There was a problem hiding this comment.
The shipped wheel turns onto an outside value with scroller.scrollTo({top, behavior: 'smooth'}) (packages/core/src/DateInput/Wheel.tsx), which the browser times, so this new timing is not met today. Please add an implementation gap here and in AST-094's gap sentence.
… are; Android claims rest on emulator evidence
|
/approve-spec b9decfd |
PR Analysis Report🔎 Preview ·
|
There was a problem hiding this comment.
Requesting changes. An engineering owner approved the motion rule and the Android evidence decision at this head, so the direction is settled; five record fixes remain:
- FR45 says every motion a tap, a key or the program starts MUST play the theme's tokens, but
spec:AST-105FR4's glide (which TreeList FR8 rides) andspec:AST-107'ssettleTostill playsprings.settle, so write that exception into FR45, the FR4 paragraph and the role table intro, or amend those records here. - AST-094's Verification "all" row and AlertDialog's FR6/PR2 row still require real Android Chrome devices against DEC-27, and the FR34/FR35 row still fails a drill-in that does not play on its roles against FR34's new theme tokens.
- DateInput FR14 now requires the theme's move tokens while the wheel turns with the browser's smooth scroll (
packages/core/src/DateInput/Wheel.tsx), so add an implementation gap there and in AST-094's gap sentence. - FR45, FR4, FR38 and DEC-26 say
character"normalizes" a spring and citespec:AST-108FR3, which says it offsets each role's measured bounce by a fixed weight, so use FR3's wording or amend FR3. - SegmentedControl FR10's Reduce Motion cross-fade now sits inside its implementation-gap sentence, so move the gap to the end of the cell.
Two smaller points: ProgressBar's new FR4 gap is not listed under Current-state impact, and AST-094's impact table omits SegmentedControl's mouse and pen release.
[Automated review]
| its `tempo` scales the spring's duration (`spec:AST-108` FR3); the follow | ||
| and the rubber band themselves stay fixed (`spec:AST-108` FR4). A part that | ||
| follows a pointer follows it under every pointer (FR43). Every other | ||
| motion, which a tap, a click, a hold, a key or the program starts, MUST |
There was a problem hiding this comment.
This MUST has no exception, but spec:AST-105 FR4 (unamended) still plays the glide on springs.settle, spec:AST-107's settleTo settles without a drag, and TreeList FR8 rides that glide for a tap's expand. The Current-state impact (lines 829-832) admits they keep their springs. Please write that exception into FR45, the FR4 paragraph (line 196) and the role table intro, or amend AST-105 FR4 and AST-107 FR2 here.
| starts at the gesture's velocity, under every pointer (FR43). A gesture is | ||
| a continuous pointer movement whose velocity carries into the motion: a | ||
| drag, a flick, a swipe, a pan, a pinch, and the settle and rubber-band | ||
| return after any of them. The theme's `character` normalizes the spring and |
There was a problem hiding this comment.
spec:AST-108 FR3 says character offsets each role's measured bounce by a fixed per-role weight and that a spring keeps its measured physics; "normalizes" reads as a different rule. Please use FR3's wording here and at lines 198, 569 and 1226, or state the new rule and amend AST-108 FR3.
| opened it (`component:DropdownMenu` FR11 and FR20). The change between | ||
| levels plays on the motion roles `springs.subMenuOpen` and | ||
| `springs.subMenuBack` (FR4); under reduced motion FR8 applies. | ||
| levels plays on the theme's move duration and easing tokens (FR45); under |
There was a problem hiding this comment.
The FR34/FR35 verification row (line 896) still fails "a change between levels that does not play on its roles", which a drill-in on the theme's move tokens now does. Please change it to "off the theme's move tokens".
| (`spec:AST-013` FR10). | ||
| recordings for every claim about the reference values, real-device | ||
| evidence for every claim about iOS Safari behavior, and, for every claim | ||
| about Android Chrome behavior, Chrome with Gboard in the Android emulator |
There was a problem hiding this comment.
The Verification "all" row (line 903) still requires "real iOS Safari and Android Chrome devices", against this sentence and DEC-27. Please change its Android part to the emulator evidence DEC-27 names.
| | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | | ||
| | FR1–FR5 | `AlertDialog.test.tsx` ("calls onOpenChange(false) on Escape", "does not call onOpenChange when action is clicked", "does not call onAction on Escape", "does not disable the cancel button while the action is loading", "exposes a named group instead, with no aria-modal") and `useImperativeAlertDialog.test.tsx` ("opens on show()", "closes on hide()") | open, loading, inline, imperative | Escape not cancelling, the action closing by itself, or the inline path claiming the role fails. | `audit:AlertDialog/behavior` | | ||
| | FR6, PR2 | Browser tests under a coarse pointer in Chromium and WebKit, a spring-curve oracle, a busy-main-thread case, and real-device clips on iOS Safari and Android Chrome | present, dismiss, close during present, reopen during dismiss, Reduce Motion, a fine pointer, a narrow viewport | a curve off its role, a start outside the viewport, a jump on interruption, a scale under Reduce Motion, changed motion under a fine pointer, or an added element fails. No test exists yet; FR6 is not shipped. | `audit:AlertDialog/behavior` | | ||
| | FR6, PR2 | Browser tests under a coarse pointer in Chromium and WebKit, a theme-token curve oracle for the present and a spring-curve oracle for the dismissal, a busy-main-thread case, and real-device clips on iOS Safari and Android Chrome | present, dismiss, close during present, reopen during dismiss, Reduce Motion, a fine pointer, a narrow viewport | a curve off its role, a start outside the viewport, a jump on interruption, a scale under Reduce Motion, changed motion under a fine pointer, or an added element fails. No test exists yet; FR6 is not shipped. | `audit:AlertDialog/behavior` | |
There was a problem hiding this comment.
This row is rewritten here but still asks for "real-device clips on iOS Safari and Android Chrome"; spec:AST-094 DEC-27 says Android claims need no human device check. Please name the Android emulator evidence instead.
| | FR13 | A bottom-sheet picker wheel that comes to rest on a disabled row (outside `min`/`max`) MUST continue to the nearest enabled row, a tie going toward the committed row, and select it there. A wheel resting on its committed row MUST NOT select or commit, even when that row is outside the bounds, so opening a bounded sheet never changes an untouched value: a committed value outside the bounds stays, and an empty field stays empty. This holds for every wheel in the DateInput, TimeInput, and DateTimeInput sheets. | DEC-1; UIDatePicker on the iOS 26.2 simulator; current source | | ||
| | FR14 | A shown bottom-sheet picker wheel whose value changes from outside, as when a bound clamps the time after another wheel turned, MUST turn onto the new row rather than jump to it. | UIDatePicker on the iOS 26.2 simulator; current source | | ||
| | FR15 | Under a coarse primary pointer, a sheet wheel's pan takes a trackpad's two-finger scroll, as `spec:AST-094` FR43 allows a record to grant: the rows follow the fingers, coast on at the lift with the fingers' last speed, and rest on a row by FR13. A mouse-wheel notch turns the wheel one row, as an arrow key does, with no coast. A ctrl-scroll stays the page's zoom (`spec:AST-094` FR43). Implementation gap: the wheel is the browser's scroll-snap column for both inputs, so a trackpad's momentum runs at the browser's rate and a mouse-wheel notch moves the column by the browser's scroll step, several rows. | `spec:AST-094` FR43; current source | | ||
| | FR14 | A shown bottom-sheet picker wheel whose value changes from outside, as when a bound clamps the time after another wheel turned, MUST turn onto the new row rather than jump to it, on the theme's move duration and easing tokens (`spec:AST-094` FR45). | UIDatePicker on the iOS 26.2 simulator; current source | |
There was a problem hiding this comment.
The shipped wheel turns onto an outside value with scrollToIndex(selectedIndex, 'smooth') (Wheel.tsx:482), which runs scroller.scrollTo({top, behavior}) (:379): the browser times it, so this new timing is not met today. Please add an implementation gap here and in AST-094's gap sentence (line 826).
| | FR9 | Under a finger (`spec:AST-094` FR13), a change of selection slides the selected surface from the old segment to the new one as one surface, the radiogroup's `::before`, animating transform and opacity only; under a fine pointer it slides on the theme's duration and easing. | Owner direction; `spec:AST-094` FR13; current source and tests | | ||
| | FR10 | The slide plays on the internal `segmented.*` motion roles with UIKit's measured values (`spec:AST-094` FR4, DEC-11; thresholds internal under `spec:AST-002` FR3); under Reduce Motion the old surface fades out in place as the new one fades in (`design:reduced-motion` DR1–DR3). With a fine pointer under Reduce Motion, the theme's transition shows the change. | UIKit measurements (iOS 26.2); `design:reduced-motion` DR1–DR3; current tests | | ||
| | FR9 | Under every pointer, a change of selection slides the selected surface from the old segment to the new one as one surface, the radiogroup's `::before`, animating transform and opacity only. A change that a tap, a click, a key or the program starts slides on the theme's duration and easing tokens, a finger included (`spec:AST-094` FR45); a drag's release settles on a spring from the release velocity (FR10, FR13). | Owner direction; `spec:AST-094` FR13; current source and tests | | ||
| | FR10 | A drag's release settles on the internal `segmented.*` motion roles with UIKit's measured values, starting at the release velocity under every pointer, with the theme's `character` and `tempo` shaping the spring (`spec:AST-094` FR4, FR45, DEC-15; thresholds internal under `spec:AST-002` FR3). Implementation gap: under a finger a tap's, a click's or a key's slide still plays `segmented.selectionSpring`, and under a mouse or a pen a drag's release slides on the theme's duration; under Reduce Motion the old surface fades out in place as the new one fades in (`design:reduced-motion` DR1–DR3). With a fine pointer under Reduce Motion, the theme's transition shows the change. | UIKit measurements (iOS 26.2); `design:reduced-motion` DR1–DR3; current tests | |
There was a problem hiding this comment.
The Reduce Motion cross-fade now follows "Implementation gap:" after a semicolon, so a required, shipped behavior reads as part of what is missing. Please end the requirement first and put the gap at the end of the cell.
…e spec's character wording, and align evidence rows and gaps
…ys the theme's exit duration and easing
There was a problem hiding this comment.
Approving. An engineering owner approved the motion rule, the touch-exit and swipe-row timing, and the Android evidence decision at this head, and the last round's blocking swipe-row finding is fixed: spec:AST-057 now carries FR45's split in its Non-goals, FR5 and FR7, with the shipped 200 ms ease-out settle recorded as an implementation gap. The spec:AST-090 Non-goals wording is fixed too.
No blocking findings remain. Two small follow-ups in spec:AST-057, fine to take here or later:
- The Contract at a glance Behavior row and the reversible-toggle, soft-delete and chooser cases still say the row "springs back" after an action fires, while FR5 now plays a tap-fired return on
--duration-exitand--ease-exit; "returns" matches FR5. - The FR7 verification row fails no close that plays off the exit tokens and no swipe close that starts from rest, unlike the other records this change moves.
A further push needs a fresh /approve-spec for its head.
[Automated review]
| | FR2 | `Item` MUST render each side's panel from its entries into an out-of-flow position on its existing root and MUST add no element to the row for it. The gesture MUST drive the row's travel by writing a custom property on the root; the root's transform MUST read it and each panel's transform its negation. | DEC-5; `useToastGesture` | Proposed; real-Chromium evidence required | | ||
| | FR3 | `swipeBehavior` MUST be one closed union, `SwipeBehavior`, with the values `reveal` and `commit`, default `reveal`, applying to both sides. `commit` with more than one entry on a side MUST warn in development, because the other entries have no touch path. | DEC-2 | Proposed | | ||
| | FR4 | A sideways drag by a finger or a pen, or a horizontal wheel gesture, MUST move the row and uncover the side's panel, showing each entry's `label` and `icon` as a filled surface in its `variant`'s colour; while a full swipe is confirmed (FR5) the outermost entry alone MUST cover the uncovered space; the panel MUST NOT be visible at rest, by any pixel. | Real-browser finding: a zero-width panel still paints its padding | Proposed; real-Chromium evidence required | | ||
| | FR5 | Under `reveal`, a release goes by its speed first: at the release speed or faster it opens the row while the row is opening and closes it while it is closing; a slower one opens the row past half the panel's width and closes it short of that. Speed alone MUST NOT fire an entry. Dragged past the confirm point, the row MUST run ahead toward its far edge with the outermost entry covering its place while the finger stays down; a lift still past the confirm point MUST fire the **outermost** entry and no other, at any speed; a swipe that confirmed and came back short of the confirm point MUST close the row and fire nothing; a disabled outermost entry MUST NOT confirm. Under `commit`, only a lift past the confirm point MUST slide the row out and fire the outermost entry; every other release closes; nothing MUST rest. After any entry fires, under either model, the row MUST return unless the entry has `hasRemoval`, in which case it MUST hold out at the edge it travelled toward. When a swipe fired the entry, the return is gesture-driven and springs from the release velocity; when a tap on the entry fired it, the return is tap-started and plays the theme's exit duration and easing, `--duration-exit` and `--ease-exit` (`spec:AST-094` FR45). That bit is the only thing an entry declares about the row; the component MUST NOT wait on, observe, or act on what the caller does next, and MUST NOT warn if a held-out row is never removed. A release short of the threshold MUST spring back under both models. Under reduced motion the slide out, the spring back, and the hold-out follow `design:reduced-motion`: the row reaches its resting place with no travel or spring and cross-fades (DR2) when a finger started the swipe, or, when a pen or a trackpad did, cross-fades on a coarse primary pointer (DR2) and arrives at once otherwise (DR3). `onActivate` MUST fire once the row's content has left its box, and may return a Promise. | DEC-1, DEC-2; UIKit `performsFirstActionWithFullSwipe` | Proposed; real-device evidence required | |
There was a problem hiding this comment.
Non-blocking: this now plays a tap-fired return on --duration-exit and --ease-exit, but the Contract at a glance Behavior row (line 57) and the reversible-toggle, soft-delete and chooser cases (lines 209, 211, 213) still say the row "springs back" after an action fires. Under reveal those actions usually fire by a tap on the resting panel. Suggest "returns" there, as here.
| | FR4 | A sideways drag by a finger or a pen, or a horizontal wheel gesture, MUST move the row and uncover the side's panel, showing each entry's `label` and `icon` as a filled surface in its `variant`'s colour; while a full swipe is confirmed (FR5) the outermost entry alone MUST cover the uncovered space; the panel MUST NOT be visible at rest, by any pixel. | Real-browser finding: a zero-width panel still paints its padding | Proposed; real-Chromium evidence required | | ||
| | FR5 | Under `reveal`, a release goes by its speed first: at the release speed or faster it opens the row while the row is opening and closes it while it is closing; a slower one opens the row past half the panel's width and closes it short of that. Speed alone MUST NOT fire an entry. Dragged past the confirm point, the row MUST run ahead toward its far edge with the outermost entry covering its place while the finger stays down; a lift still past the confirm point MUST fire the **outermost** entry and no other, at any speed; a swipe that confirmed and came back short of the confirm point MUST close the row and fire nothing; a disabled outermost entry MUST NOT confirm. Under `commit`, only a lift past the confirm point MUST slide the row out and fire the outermost entry; every other release closes; nothing MUST rest. After any entry fires, under either model, the row MUST return unless the entry has `hasRemoval`, in which case it MUST hold out at the edge it travelled toward. When a swipe fired the entry, the return is gesture-driven and springs from the release velocity; when a tap on the entry fired it, the return is tap-started and plays the theme's exit duration and easing, `--duration-exit` and `--ease-exit` (`spec:AST-094` FR45). That bit is the only thing an entry declares about the row; the component MUST NOT wait on, observe, or act on what the caller does next, and MUST NOT warn if a held-out row is never removed. A release short of the threshold MUST spring back under both models. Under reduced motion the slide out, the spring back, and the hold-out follow `design:reduced-motion`: the row reaches its resting place with no travel or spring and cross-fades (DR2) when a finger started the swipe, or, when a pen or a trackpad did, cross-fades on a coarse primary pointer (DR2) and arrives at once otherwise (DR3). `onActivate` MUST fire once the row's content has left its box, and may return a Promise. | DEC-1, DEC-2; UIKit `performsFirstActionWithFullSwipe` | Proposed; real-device evidence required | | ||
| | FR6 | The drag MUST decide its axis once, early, and a mostly vertical drag MUST stay the scroller's. A mouse MUST NOT start the drag. The click the browser synthesizes after a drag MUST NOT fire the row's primary action. | Press-model ORD1 | Proposed; real-device evidence required | | ||
| | FR7 | An open row (resting open, or settling open from the release that opened it) MUST close on a pointer landing anywhere outside it, on a touch tap on its own content, on a scroll of anything that holds it while focus is outside its open panel, on Escape with focus inside it, on firing an entry, and on unmount. That tap MUST activate nothing and paint no press, an exception to the cell press profile of `spec:AST-094` FR11. Because a drag on a neighbour is a pointer outside it, at most one row is open at a time without any shared state. A close that a tap, a pointer outside, a scroll, Escape, a fired entry's tap or the program starts plays the theme's exit duration and easing, `--duration-exit` and `--ease-exit`; a close a swipe drives keeps its measured spring from the release velocity (`spec:AST-094` FR45). Implementation gap: the shipped row settles every close on one 200 ms ease-out today. | DEC-3; SwiftUI `swipeActionsContainer()` for the outcome | Proposed | |
There was a problem hiding this comment.
Non-blocking: the FR7 verification row (line 352) fails no close that plays off --duration-exit and --ease-exit, and no swipe close that starts from rest. The Switch, SegmentedControl, AlertDialog, ContextMenu and ProgressBar rows gained that failure signal in this change; adding both cases here keeps AST-057 in step.
…prings # Conflicts: # docs/architecture/react-component-runtime.md # docs/specs/AST-099-mobile-keyboard/spec.md
|
/approve-spec 6ea73c7 |
There was a problem hiding this comment.
Approved pending CI: the heavy test, build and docsite-test checks are deferred on this head and run before merge; Lint and typecheck pass.
An engineering owner approved the spec change at this exact head, and the merge from main keeps the mobile keyboard spec's new FR9 rows and the runtime architecture's INV11 text intact beside your edits. Two optional follow-ups in spec:AST-057:
- The Behavior row (line 57) and the reversible-toggle, soft-delete and chooser cases (lines 209, 211, 213) still say the row "springs back" after an action fires, while FR5 now plays a tap-fired return on
--duration-exitand--ease-exit; "returns" would match FR5. - The FR7 verification row (line 352) could fail a tap or Escape close that plays off
--duration-exitand--ease-exit, and a swipe close that starts from rest, as the other records this change moves now do.
[Automated review]
…prings # Conflicts: # docs/specs/components/DateInput/DateInput.spec.md
|
/approve-spec 3eb7fad |
There was a problem hiding this comment.
Approved pending CI: the heavy test, build and docsite-test checks are deferred on this head and run before merge, and Lint did not complete on this head.
An engineering owner approved the spec change at this exact head, and the merge from main keeps the DateInput FR14 rule that a tapped wheel row commits when its turn ends. Optional record follow-ups:
spec:AST-057: the Behavior row (line 57) and the reversible-toggle, soft-delete and chooser cases (lines 209, 211, 213) still say the row "springs back" after an action fires, while FR5 now plays a tap-fired return on--duration-exitand--ease-exit; say "returns".spec:AST-057: the FR7 verification row (line 352) fails no close that plays off the exit tokens and no swipe close that starts from rest.- Miss: the DateInput FR14 verification row (line 341) fails no turn that plays a measured role instead of the theme's move duration and easing, which FR14 now requires.
- Miss: Lightbox FR12 (line 147) times every return into the source on the exit tokens without saying whether a drag-down or pinch-in close returns into it; if one does,
spec:AST-094FR7 and FR45 give that return a spring from the release velocity.
[Automated review]
| | FR9 | With `hasZoom`, a pinch in from the fitted size is a release decision: below the measured fraction of the fitted size the release closes the viewer; opening the fingers again by the measured amount before the lift is moving back and cancels. A pinch that starts zoomed in never closes the viewer. | `spec:AST-094` FR36, FR37 | settled; implementation gap | | ||
| | FR10 | With `hasZoom`, under a coarse pointer only, a tap on the photo fades the controls and caption and darkens the backdrop. While they are hidden, the next tap on the photo, a tap beside the photo, or a cancelled drag brings them back, and that tap beside the photo does not close the viewer (ORD3). AR5 governs keyboard and assistive-technology access while they are hidden. | `architecture:public-component-api` INV16 | settled; implementation gap | | ||
| | FR11 | A mouse, a pen, and a trackpad also use FR7's drag paging, FR8's drag to close, a ctrl-wheel or trackpad pinch over the photo for FR6's zoom and FR9's close, and a wheel to pan a zoomed photo; over the photo these replace the browser's page zoom and scroll. A double-click zooms 2× about the clicked point. A click on the photo keeps the controls, and the viewer opens and closes at once. | `spec:AST-094` FR43; `spec:AST-017` FR59 | settled; implementation gap | | ||
| | FR12 | Inside `experimental` only, under a coarse pointer, the photo grows out of its source when the viewer opens, on the theme's duration and easing tokens (`spec:AST-094` FR45), and returns into it when it closes, on the theme's exit duration and easing (`spec:AST-094` FR7): the element `experimental.sourceFor(index)` returns, else the opener when it shows a picture. A close reports `onOpenChange(false)` at the request; the viewer stays only until the photo lands. Pointer input during the entrance is held; keys and assistive technology stay live. Without `experimental`, opening is unchanged and closing plays FR16. | `spec:AST-017` FR21; `spec:AST-094` FR35 | settled; implementation gap | |
There was a problem hiding this comment.
Miss (non-blocking): this times every return into the source on the theme's exit duration and easing, but does not say whether a drag-down (FR8) or pinch-in (FR9) close returns into the source. If one does, that return is gesture-driven, and spec:AST-094 FR7 and FR45 give it a spring from the release velocity. Suggested wording: a return that a tap, a key or the program starts plays the exit tokens; one a drag or a pinch drives springs from the release.
…prings # Conflicts: # docs/specs/components/DateInput/DateInput.spec.md
|
/approve-spec 7fc3273 |
There was a problem hiding this comment.
Approved pending CI: the heavy build, test and docsite-test checks are deferred on this head and run before merge; lint and typecheck pass.
An engineering owner approved the spec change at this exact head, and the merge from main keeps the DateInput FR14 rule that a tapped wheel row commits when its turn ends. Optional record follow-ups:
- Regression:
spec:AST-094's DateInput row (line 892) and its gap list (lines 846-850) still give the wheel's turn today as the tap spin only, but since main scoped that to a finger, DateInput FR14 records that the mouse column turns by the browser's smooth scroll, so add "under a finger" and a mouse line. - Miss: DateInput's Compatibility/readiness row (line 64), Compatibility class (lines 79-81) and Current-state impact name neither FR14's theme-timed turn under every pointer nor FR15's trackpad wherever the sheet shows, as Switch, Slider and ProgressBar name theirs.
spec:AST-057: the Behavior row (line 57) and the reversible-toggle, soft-delete and chooser cases (lines 209, 211, 213) still say the row "springs back" after an action fires, while FR5 now plays a tap-fired return on--duration-exitand--ease-exit; say "returns".spec:AST-057: the FR7 verification row (line 352) fails no close that plays off the exit tokens and no swipe close that starts from rest.- DateInput's FR14 verification row (line 343) fails no turn that plays a measured role or the browser's smooth scroll instead of the theme's move duration and easing, which FR14 now requires.
- Lightbox FR12 (line 147) times every return into the source on the exit tokens without saying whether a drag-down or pinch-in close returns into it; if one does,
spec:AST-094FR7 and FR45 give that return a spring from the release velocity.
[Automated review]
| | Lightbox | Controls fade on a tap, a double tap's zoom, the grow from its source | Named measured roles | Theme tokens | | ||
| | TreeList | Group slide under a coarse pointer | Measured motion | Theme tokens | | ||
| | ProgressBar | Determinate fill on a value change under a coarse pointer | Linear, `progress.fillTrackMs` times the share moved | `--duration-medium` and the theme's easing, every pointer | | ||
| | DateInput | Sheet wheel turning onto a clamped or a tapped row | `picker.tapSpin.durationMs` on `timing.curves.move` | Theme move tokens | |
There was a problem hiding this comment.
Regression (non-blocking): this row gives the turn today as picker.tapSpin.durationMs only, but since main scoped that to a finger, DateInput FR14 records that the mouse column turns onto a clamped value by the browser's smooth scroll, which FR45 also moves. Say "under a finger" here and add a mouse line ("Browser smooth scroll | Theme move tokens"); the gap list at lines 846-850 needs the same.
| | FR13 | A bottom-sheet picker wheel that comes to rest on a disabled row (outside `min`/`max`) MUST continue to the nearest enabled row, a tie going toward the committed row, and select it there. A wheel resting on its committed row MUST NOT select or commit, even when that row is outside the bounds, so opening a bounded sheet never changes an untouched value: a committed value outside the bounds stays, and an empty field stays empty. This holds for every wheel in the DateInput, TimeInput, and DateTimeInput sheets. | DEC-1; UIDatePicker on the iOS 26.2 simulator; current source | | ||
| | FR14 | A shown bottom-sheet picker wheel whose value changes from outside, as when a bound clamps the time after another wheel turned, MUST turn onto the new row rather than jump to it. Under a finger, the turn plays the internal motion role `picker.tapSpin.durationMs`, UIKit's measured 300 ms, on `timing.curves.move`, as a tap on a row does. Under a finger, a tapped row commits its value when its turn ends; a keyboard or assistive-technology activation commits at once. | UIDatePicker on the iOS 26.2 simulator; current source | | ||
| | FR15 | Under a coarse primary pointer, a sheet wheel's pan takes a trackpad's two-finger scroll, as `spec:AST-094` FR43 allows a record to grant: the rows follow the fingers, coast on at the lift with the fingers' last speed, and rest on a row by FR13. A mouse-wheel notch turns the wheel one row, as an arrow key does, with no coast. A ctrl-scroll stays the page's zoom (`spec:AST-094` FR43). | `spec:AST-094` FR43; current source | | ||
| | FR14 | A shown bottom-sheet picker wheel whose value changes from outside, as when a bound clamps the time after another wheel turned, MUST turn onto the new row rather than jump to it, on the theme's move duration and easing tokens, as a tap on a row does (`spec:AST-094` FR45). The internal motion role `picker.tapSpin.durationMs`, UIKit's measured 300 ms, stays as the measured reference. Under a finger, a tapped row commits its value when its turn ends; a keyboard or assistive-technology activation commits at once. Implementation gap: under a finger, the turn and a tapped row's turn still play `picker.tapSpin.durationMs` on `timing.curves.move`; under a mouse, the turn plays the browser's smooth scroll. | UIDatePicker on the iOS 26.2 simulator; current source | |
There was a problem hiding this comment.
Miss (non-blocking): this row now turns the wheel on the theme's move tokens under every pointer, and FR15 below lets a trackpad drive it wherever the sheet shows, but the Compatibility/readiness row (line 64), the Compatibility class (lines 79-81) and Current-state impact name neither change; Switch, Slider and ProgressBar name theirs. The FR14 verification row (line 343) also still lists the tap-spin proof and fails no turn off the theme's move tokens.
Spec only. Maintainer impact: none on any release. This PR changes no code, and no release gate reads it.
Exact decision recorded
One motion rule for every pointer (touch presentation spec FR45, DEC-26):
temposcales the spring's duration andcharacteroffsets each role's measured bounce by a fixed weight, as the theme motion spec states. A part that follows a pointer follows every pointer.tempo, under every pointer, a finger included. A tap has no velocity, so it is not a gesture. A touch presentation keeps its look (a menu grows from its trigger, a thumb lifts) and plays it on those tokens.animate(public animator spec FR3), and the snap-point drag'ssettleTo(public animator spec).--duration-exitand--ease-exit; an exit a gesture drives keeps its velocity spring. The theme's exit switches still never remove a touch exit: the exit motion spec's FR14 and DEC-5 change only their timing clause.A second decision (DEC-27): Android Chrome claims are met by emulator evidence (Chrome with Gboard in the Android emulator, at the exact head, with animations at normal scale and removed, plus a slow-device profile), with no human device check. iOS Safari claims keep real-device evidence. The mobile keyboard spec's evidence text and its DEC-3 (now: a physical iPhone gates release) are aligned.
What changes for people
Motion that a tap, a hold or a key starts under a finger moves from a measured spring to the theme's tokens:
Motion that a gesture drives now springs, and follows, under every pointer:
Unchanged: whether a touch exit plays (no exit switch removes it), press feedback timing, gesture thresholds, the follow and the rubber band themselves (fixed), Toast's swipe, BottomSheet's pull, Lightbox's drag paging, drag to close and pinch, a row's swipe, and reduced motion.
Existing authority searched
The touch presentation spec (Intent, Non-goals, FR1, FR4, FR7, FR13, FR31, FR33, FR34, FR38, FR43, FR44, DEC-1, DEC-3, DEC-15, DEC-25, Platform support), the theme motion spec (FR2–FR4), the exit motion spec (FR14, DEC-5), the mobile keyboard spec (Platform support, DEC-3), the reduced-motion design rules, the generated motion role table, and the Switch, Slider, SegmentedControl, Carousel, ContextMenu, DropdownMenu, Popover, AlertDialog, Lightbox, DateInput, TreeList and ProgressBar records. No open pull request claims FR45, DEC-26 or DEC-27.
Decision
Rejected:lines), and DEC-27 (Android emulator evidence). FR1 and FR4 limit measured roles to gesture motion (tap-started touch exits play the theme's exit tokens), and name measured input timings as internal values, not roles. Intent, Non-goals, FR13, FR31, FR33, FR34, FR38 and FR43 point to FR45. Platform support takes DEC-27's evidence rule. Current-state impact lists every record the rule moves.--duration-exitand--ease-exit; the switch clause is unchanged. FR7 in the touch spec says the same.--duration-exitand--ease-exit, BottomSheet's close included, with an implementation gap (a coarse-pointer close started by a tap or key plays--duration-mediumtoday).--duration-exitand--ease-exit; a close a swipe drives keeps its measured spring (FR5, FR7). The Non-goals' source-constants line now covers only gesture-driven slides and springs. Implementation gap: the shipped row settles every close on one 200 ms ease-out today.Rejected alternatives
Claim scope and non-goals
animate, and the snap-point drag'ssettleTokeep their springs, named as an exception in FR45, FR4 and the role table intro. Their own records decide any change.Evidence and implementation relationship
Owner decisions of 2026-11-02 (motion) and 2026-11-03 (Android evidence), recorded in plain words in DEC-26 and DEC-27. No implementation in this pull request.
Writing rubric results
Rejected:lines in DEC-26 and one in DEC-27.Scope
Validation
pnpm install --frozen-lockfile: cleanpnpm check:knowledge: alignedpnpm check:motion-roles: in sync (34 roles)