Repository navigation
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 waits on an engineering-owner decision: under a coarse pointer this change replaces the pressed overlay that the current design:user-states DR1 requires for the Switch track and thumb, and it builds on the draft spec:AST-094, which no owner has approved 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), with the DR1 Switch row amended for coarse pointers; otherwise Switch keeps the overlay on touch. Whatever the decision, these need changes:
- Land the stacked predecessors and rebase, so this pull request holds only the Switch commit instead of 141 files and about 25,700 changed lines.
- Make the page-wide text-selection guard shared across switches, because tapping a second switch within half a second of the first leaves
user-select: noneon<html>until reload (useSwitchTouch.ts). - Paint the checked colour through the
switchtheme target again under a finger, and correct the changeset, which says scrolling never waits although a scroll that starts on a switch waits for a busy main thread in the default mode.
[Automated review]
| const previous = ['-webkit-user-select', 'user-select'].map( | ||
| property => [property, root.style.getPropertyValue(property)] as const, | ||
| ); | ||
| for (const [property] of previous) { | ||
| root.style.setProperty(property, 'none'); | ||
| } | ||
| this.unguardSelection = () => { | ||
| for (const [property, value] of previous) { | ||
| if (value === '') { | ||
| root.style.removeProperty(property); | ||
| } else { | ||
| root.style.setProperty(property, value); | ||
| } | ||
| } | ||
| }; |
There was a problem hiding this comment.
Each switch saves and restores the root's inline user-select on its own. Tap switch A, then switch B within 500 ms: B saves A's none, A's timer restores '', then B's timer restores none, and every later guard saves and restores that none, so <html> stays unselectable until reload. A single per-document guard with a count (save on the first acquire, restore on the last release) avoids it; a browser test that taps two switches inside the window would cover it.
There was a problem hiding this comment.
Fixed by removal: the page-wide guard is gone. The fourth commit locks text selection on the input the finger lands on (an input's own -webkit-user-select: text ignores its ancestors, which is what opened the loupe), and the stacked gesture-host change locks held press trackers, so nothing writes to <html> any more.
| trackOnLayer: { | ||
| display: {default: 'none', [TOUCH]: 'block'}, | ||
| position: 'absolute', | ||
| inset: 0, | ||
| borderRadius: 'inherit', | ||
| pointerEvents: 'none', | ||
| backgroundColor: { | ||
| default: colorVars['--color-accent'], | ||
| '@media (forced-colors: active)': 'Highlight', | ||
| }, | ||
| }, |
There was a problem hiding this comment.
Under a coarse pointer this untargeted layer paints --color-accent over the switch theme target whenever the switch is on, so a theme that sets the target's checked fill (the probe theme does, probeTheme.ts:4567-4568) no longer shows it on touch devices. Keeping the checked fill on the switch target, or having this layer read a variable the target's checked rule sets, keeps the theming surface intact.
There was a problem hiding this comment.
Fixed in the sixth commit. Under a finger the track now keeps its own fill in both states, so the switch target paints it as it does under a mouse. The fade layer over it no longer paints the accent: it takes the track's fill in the other state, read from the track itself (switchFill.ts swaps the track to that state's class and attributes, reads the computed colour and swaps back in the same task), and fades it on the compositor. New browser cases check the probe theme's fill under a finger, off and on, on iOS and Android; they fail before this commit.
| '@astryxdesign/core': patch | ||
| --- | ||
|
|
||
| [feat] On a touch screen, Switch behaves like the iOS switch. A press lifts the thumb into a glass lens; a horizontal drag moves the thumb with the finger and flips the switch once the finger has travelled most of the track; lifting the finger commits, and a tap or a hold toggles. A vertical drag that starts on a switch scrolls the page, as before. The motion runs on the compositor, so it stays smooth while scripts run, and the switch never makes the page's scrolling wait for script. Mouse, keyboard and assistive technology behave as before. |
There was a problem hiding this comment.
"The switch never makes the page's scrolling wait for script" does not hold for the default claiming mode (SWITCH_CLAIMS_HELD_TOUCH = true): its non-passive touchmove listener makes a scroll that starts on a switch wait for a busy main thread, as switchTouchClaim.ts and the pull request body say. Stating that limit here would keep the release note accurate.
There was a problem hiding this comment.
Fixed: the changeset now says that a finger resting on the switch first keeps the touch, as in a UIKit list, so a scroll that starts on the switch waits for a busy main thread.
PR Analysis Report📚 Storybook Preview🧪 Sandbox PreviewModified ComponentsButton (@astryxdesign/core) · View in Storybook
ClickableCard (@astryxdesign/core) · View in Storybook
Item (@astryxdesign/core) · View in Storybook
SelectableCard (@astryxdesign/core) · View in Storybook
Switch (@astryxdesign/core) · View in Storybook
Thumbnail (@astryxdesign/core) · View in Storybook
TreeList (@astryxdesign/core) · View in Storybook
Component Size
Sizes are gzip. Import counts the JavaScript the import loads eagerly; CSS counts the package stylesheet rules referenced by the modules it can load from its own package, lazily loaded ones included.
|
| Component | JavaScript | CSS |
|---|---|---|
| core/AlertDialog | +3.3KB |
+805B |
| core/AppShell | +3.1KB |
+811B |
| core/AvatarGroupOverflow | +2.7KB |
+234B |
| core/Banner | +3.3KB |
+807B |
| core/BaseTypeahead | +3.3KB |
+816B |
| core/BreadcrumbItem | +3.7KB |
+1.0KB |
| core/Button | +3.3KB |
+806B |
| core/Calendar | +3.3KB |
+801B |
| core/Carousel | +3.3KB |
+805B |
| core/ChatComposer | +3.3KB |
+805B |
| core/ChatComposerInput | +3.3KB |
+808B |
| core/ChatComposerTokenElement | +3.3KB |
+808B |
| core/ChatDictationButton | +3.3KB |
+805B |
| core/ChatLayout | +3.3KB |
+803B |
| core/ChatLayoutScrollButton | +3.3KB |
+805B |
| core/ChatSendButton | +3.3KB |
+804B |
| core/CheckboxInput | +2.7KB |
+237B |
| core/CheckboxListItem | +3.3KB |
+767B |
| core/ClickableCard | +3.1KB |
+813B |
| core/CodeBlock | +3.3KB |
+819B |
| core/Collapsible | +2.7KB |
+260B |
| core/CommandPalette | +2.8KB |
+236B |
| core/CommandPaletteItem | +2.7KB |
+264B |
| core/ComplexSelector | +3.4KB |
+799B |
| core/ContextMenu | +3.7KB |
+1.1KB |
| core/ContextMenuItem | +3.3KB |
+805B |
| core/DateInput | +3.5KB |
+807B |
| core/DateRangeInput | +3.4KB |
+794B |
| core/DateTimeInput | +3.5KB |
+818B |
| core/DialogHeader | +3.4KB |
+807B |
| core/DrawerHeader | +3.4KB |
+806B |
| core/DropdownMenu | +3.7KB |
+1.2KB |
| core/DropdownMenuCheckboxItem | +3.2KB |
+637B |
| core/DropdownMenuItem | +3.2KB |
+657B |
| core/DropdownMenuRadioItem | +3.1KB |
+637B |
| core/DropdownMenuSubMenu | +3.1KB |
+631B |
| core/FileInput | +3.4KB |
+795B |
| core/IconButton | +3.3KB |
+806B |
| core/Item | +3.4KB |
+659B |
| core/LayerProvider | +3.2KB |
+812B |
| core/Lightbox | +3.3KB |
+808B |
| core/Link | +2.6KB |
+237B |
| core/ListItem | +3.2KB |
+767B |
| core/Markdown | +3.4KB |
+1.1KB |
| core/MobileNav | +3.2KB |
+809B |
| core/MobileNavToggle | +3.3KB |
+803B |
| core/MoreMenu | +3.6KB |
+1.2KB |
| core/MultiSelector | +3.2KB |
+796B |
| core/NumberInput | +3.4KB |
+805B |
| core/Outline | +2.6KB |
+261B |
| core/Pagination | +3.4KB |
+1.0KB |
| core/Popover | +3.1KB |
+797B |
| core/PowerSearch | +3.5KB |
+1.0KB |
| core/RadioListItem | +3.2KB |
+626B |
| core/SegmentedControlItem | +2.7KB |
+257B |
| core/SelectableCard | +3.1KB |
+813B |
| core/Selector | +3.5KB |
+1.0KB |
| core/SelectorOption | +3.3KB |
+656B |
| core/SideNav | +3.3KB |
+809B |
| core/SideNavCollapseButton | +3.3KB |
+802B |
| core/SideNavHeading | +3.2KB |
+809B |
| core/SideNavItem | +3.3KB |
+812B |
| core/Step | +2.7KB |
+252B |
| core/Stepper | +3.3KB |
+803B |
| core/Switch | +32.4KB |
+1.1KB |
| core/Tab | +2.7KB |
+253B |
| core/TabMenu | +3.3KB |
+801B |
| core/Table | +3.6KB |
+1.1KB |
| core/TableCell | +3.7KB |
+1.1KB |
| core/TableHeaderCell | +3.7KB |
+1.2KB |
| core/TableSelectionToolbar | +3.3KB |
+812B |
| core/TextInput | +3.4KB |
+808B |
| core/Thumbnail | +3.3KB |
+800B |
| core/TimeInput | +3.3KB |
+799B |
| core/Toast | +3.4KB |
+806B |
| core/ToggleButton | +3.3KB |
+806B |
| core/Token | +2.7KB |
+262B |
| core/Tokenizer | +3.0KB |
+804B |
| core/TopNav | +3.4KB |
+819B |
| core/TopNavHeading | +3.2KB |
+816B |
| core/TopNavItem | +2.7KB |
+261B |
| core/TopNavMegaMenu | +3.3KB |
+810B |
| core/TopNavMegaMenuItem | +2.7KB |
+261B |
| core/TopNavMenu | +3.2KB |
+812B |
| core/TreeList | +3.3KB |
+657B |
| core/Typeahead | +3.4KB |
+800B |
Bundle Size Summary
Gzip figures measure each package's entry file, not the code an import pulls in.
| Package | Entry (ESM) | Entry gzip before | Entry gzip after | Delta |
|---|---|---|---|---|
| @astryxdesign/core | 4.9KB | 1.2KB | 1.2KB | 0B |
| Package stylesheet | Gzip before | Gzip after | Delta |
|---|---|---|---|
| @astryxdesign/core | 34.6KB | 36.3KB | +1.8KB |
Accessibility Audit
Status: 7 accessibility violation(s) found — 7 serious.
Button pattern - 1 issue(s)
- 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
- Rule:
color-contrast· Affects 1/10 stories · Learn more - WCAG: 1.4.3 (Level AA)
- Rule:
Checkbox pattern - 1 issue(s)
- 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
- Rule:
color-contrast· Affects 1/3 stories · Learn more - WCAG: 1.4.3 (Level AA)
- Rule:
ClickableCard - 1 issue(s)
- 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
- Rule:
color-contrast· Affects 1/5 stories · Learn more - WCAG: 1.4.3 (Level AA)
- Rule:
Radio group pattern - 1 issue(s)
- 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
- Rule:
color-contrast· Affects 3/11 stories · Learn more - WCAG: 1.4.3 (Level AA)
- Rule:
RadioList - 1 issue(s)
- 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
- Rule:
color-contrast· Affects 3/14 stories · Learn more - WCAG: 1.4.3 (Level AA)
- Rule:
SelectableCard - 1 issue(s)
- 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
- Rule:
color-contrast· Affects 1/4 stories · Learn more - WCAG: 1.4.3 (Level AA)
- Rule:
Thumbnail - 1 issue(s)
- 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
- Rule:
color-contrast· Affects 1/12 stories · Learn more - WCAG: 1.4.3 (Level AA)
- Rule:
Visual Regression
55 of 502 shot(s) changed.
A change here is a question, not a failure: check whether the after is the
picture you intended. Record that review on the PR. Baseline maintenance is an
explicit dispatch of CI; this report never rewrites the baseline or adds a release gate.
| component | story | theme | mode | pixels |
|---|---|---|---|---|
| Selector | Indicator Space Bottom Sheet Narrow End RTL | neutral | light | 753,933 |
| Selector | Indicator Space Bottom Sheet Wide Start | neutral | light | 740,072 |
| RichTextEditor | Markdown parity: side by side | probe | dark | 558,840 |
| RichTextEditor | Markdown parity: side by side | probe | light | 540,756 |
| Chat | Full AI Chat | probe | light | 230,823 |
| Chat | Full AI Chat | probe | dark | 229,805 |
| AppShell | With Mobile Nav | probe | light | 217,010 |
| AppShell | With Mobile Nav | probe | dark | 216,960 |
| Collapsible | Dividers | probe | dark | 66,445 |
| Collapsible | Dividers | probe | light | 66,445 |
| AppShell | Playground | neutral | dark | 65,198 |
| AppShell | Playground | neutral | light | 63,788 |
| Chat | Default | neutral | dark | 36,304 |
| Chat | Default | neutral | light | 35,767 |
| Chat | Empty State | probe | light | 23,531 |
| Chat | Empty State | probe | dark | 22,622 |
| ChatAutoScroll | Scroll Behavior Comparison | neutral | dark | 15,378 |
| ChatAutoScroll | Scroll Behavior Comparison | neutral | light | 14,107 |
| Chat | With Attachments | probe | light | 13,406 |
| Chat | With Attachments | probe | dark | 11,232 |
and 35 more.
Generated by PR Enrichment workflow | Storybook | Sandbox | View full report
02bed51 to
69e4689
Compare
There was a problem hiding this comment.
Approval waits on an engineering-owner decision: under a coarse pointer this change replaces the pressed overlay that the current design:user-states DR1 requires for the Switch track and thumb, and it implements the draft spec:AST-094, which no owner has approved 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), with the DR1 Switch row amended for coarse pointers; otherwise Switch keeps the overlay on touch. Whatever the decision, these need changes:
- Land the stacked predecessors and rebase, so this pull request holds only the Switch commits instead of 141 files and about 26,000 changed lines; those commits alone are still about 2,270 lines.
- Make the page-wide text-selection guard shared across switches, because tapping a second switch within half a second of the first leaves
user-select: noneon<html>until reload (useSwitchTouch.ts). - Paint the checked colour through the
switchtheme target again under a finger, and correct the changeset, which says scrolling never waits although a scroll that starts on a switch waits for a busy main thread in the default mode. - Fix the lint error at
Switch.interaction.browser.spec.ts:203, whereNumber()wraps a value that is already a number.
[Automated review]
| const effect = travel.effect as KeyframeEffect; | ||
| const offsets = effect | ||
| .getKeyframes() | ||
| .map(k => Number(k.computedOffset)) |
There was a problem hiding this comment.
k.computedOffset is already a number, so this Number() trips @typescript-eslint/no-unnecessary-type-conversion, the one error that fails the lint job on this revision. .map(k => k.computedOffset) keeps the same values.
There was a problem hiding this comment.
Fixed: the Number() is gone and lint passes.
05e7024 to
73803ee
Compare
There was a problem hiding this comment.
Approval waits on an engineering-owner decision: under a coarse pointer this change replaces the pressed overlay that the current design:user-states DR1 requires for the Switch track and thumb, and it implements the draft spec:AST-094, which no owner has approved 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), with the DR1 Switch row amended for coarse pointers; otherwise Switch keeps the overlay on touch. Whatever the decision, these need changes:
- Land the stacked predecessors and rebase, so this pull request holds only the Switch commits instead of 141 files and about 26,000 changed lines; those commits alone are still about 2,270 lines.
- Make the page-wide text-selection guard shared across switches, because tapping a second switch within half a second of the first leaves
user-select: noneon<html>until reload (useSwitchTouch.ts). - Paint the checked colour through the
switchtheme target under a finger, and state in the changeset that a finger resting on a switch keeps the touch from scrolling and that a scroll starting on a switch waits for a busy main thread. - Move the interaction proofs into an existing CI lane instead of the separately routed
pr-interactionjob, whichspec:AST-030FR11 rules out for new coverage inside an existing surface.
[Automated review]
| # them, so a change to an instrument is proven by its negative controls | ||
| # before a component spec relies on it. Chromium only on these runners; | ||
| # see the harness README for running WebKit. | ||
| pr-interaction: |
There was a problem hiding this comment.
pr-interaction routes its own scope (Check interaction scope below) over package, Storybook and harness paths. spec:AST-030 FR11 says new coverage inside an existing surface "MUST join those owners and MUST NOT create a separately routed lane or required context", so these proofs need to join an existing owner lane, or FR11 needs amending first.
There was a problem hiding this comment.
The pr-interaction job and its routing come from the interaction-harness PRs this one is stacked on, not from this pull request's own commits, so the AST-030 FR11 question belongs to that harness PR; I have passed it on.
There was a problem hiding this comment.
Approval waits on an engineering-owner decision: under a coarse pointer this change replaces the pressed overlay that the current design:user-states DR1 requires for the Switch track and thumb, and it implements the draft spec:AST-094, which no owner has approved 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), with the DR1 Switch row amended for coarse pointers; otherwise Switch keeps the overlay on touch. The latest commit's fix checks out, since under a mouse the switch now looks exactly as it does without this change, but whatever the decision these still need changes:
- Land the stacked predecessors and rebase, so this pull request holds only the Switch commits instead of 141 files and about 26,000 changed lines; those commits alone are still about 2,400 lines.
- Share the page-wide text-selection guard across switches, because tapping a second switch within half a second of the first leaves
user-select: noneon<html>until reload (useSwitchTouch.ts). - Paint the checked colour through the
switchtheme target under a finger, since the new on layer hides a theme's checked fill (Switch.tsx). - State in the changeset that a finger resting on a switch keeps the touch from scrolling and that a scroll starting on a switch waits for a busy main thread.
- Move the interaction proofs into an existing CI lane instead of the separately routed
pr-interactionjob, whichspec:AST-030FR11 rules out.
[Automated review]
b0b01b3 to
30279c6
Compare
|
Replies to the automated review:
|
|
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. |
There was a problem hiding this comment.
Approval waits on an engineering-owner decision: on iPhone and iPad this change replaces the Switch's pressed overlay, which the current design:user-states DR1 requires, with the UISwitch touch presentation of the draft spec:AST-094, which no owner has approved. 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), with the DR1 Switch row amended for iOS touch; otherwise the Switch keeps the overlay on iOS. The earlier selection-lock, theme-fill, changeset and lint findings are fixed, but whatever the decision these still need changes:
- Under Reduce Motion, make the iOS Switch cross-fade in 200 ms or less instead of lifting, travelling and springing (
reducedMotion: 'keep'inuseSwitchTouch.ts), as the currentdesign:reduced-motionDR1, DR2 and DR4 require. - Land the stacked predecessors and rebase, so this pull request holds only the Switch commits instead of 181 files and about 32,500 changed lines; those commits alone are still about 3,100 lines.
- In the predecessor pull requests, keep the reduced-motion duration out of the themable motion roles (drop the 300 ms example), use the theme's compact definition instead of 744 px as
spec:AST-012FR9 requires, and movepr-interactioninto an existing CI lane asspec:AST-030FR11 requires.
[Automated review]
| thumb.animate( | ||
| {x: this.toX(dragTarget(drag.start, x, this.geometry))}, | ||
| {spring: TRACKING}, | ||
| {at: time, reducedMotion: 'keep'}, |
There was a problem hiding this comment.
Every animation in this controller passes reducedMotion: 'keep' (also lines 736, 761, 770 and 821), so with Reduce Motion on, the iOS Switch still lifts, travels and springs; in Chromium a remote turn-on travels for about 460 ms. The current design:reduced-motion DR1, DR2 and DR4 allow only an opacity cross-fade of 200 ms or less here and exempt only content that follows the finger, so 'keep' fits the drag's tracking spring alone.
| * // A softer sheet and a slower reduced-motion fade: | ||
| * motion: { | ||
| * springs: { 'springs.sheetPresent': { duration: 0.6, bounce: 0.1 } }, | ||
| * durations: { 'timing.reducedMotionCrossFadeMs': 300 }, |
There was a problem hiding this comment.
This example, repeated in the CLI theme template, lengthens the reduced-motion cross-fade to 300 ms. design:reduced-motion DR2 caps a touch cross-fade at 200 ms and DR4 says theme values offer no way to keep a longer animation under reduced motion, so timing.reducedMotionCrossFadeMs belongs with press.scrollBrakeMs among the values a theme cannot retune.
| 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 defines compact as a width below the effective md point or a height below 500, and says the web touch presentation resolver keeps no size numbers of its own. With the default md of 768, coarse screens 744 to 767 px wide get the popover instead of the sheet in the five adaptive menus, and a theme that moves md moves nothing here.
| # them, so a change to an instrument is proven by its negative controls | ||
| # before a component spec relies on it. Chromium only on these runners; | ||
| # see the harness README for running WebKit. | ||
| pr-interaction: |
There was a problem hiding this comment.
spec:AST-030 FR11 says new coverage inside an existing surface "MUST NOT create a separately routed lane or required context", and this job routes its own scope (Check interaction scope below). It needs to join an existing lane in the harness pull request, or FR11 amended first.
|
Agreed: one implementation.
On INV16: the Switch has no open surface, so, like Slider, it takes no |
…or and an Android 15 emulator
30279c6 to
9ed10c8
Compare
|
The converged head is up: 9ed10c8. The Switch's touch presentation keys only on the resolver's coarse pointer (no platform gate), reduced motion goes through the animator's policy, and the fade layer and the lens are now pseudo-elements on the track and the thumb box. |
…ents asks The host counts the attachments that pass actionGroup: true and keeps the element a group while one remains, recomputed at each detach. An attachment that passes false, as useGestureRecognizer does by default, no longer turns a group off, and a group's last asking attachment taking its leave ends the group.
… that content The presented hit test takes the document's elementFromPoint, which answers a shadow host for anything inside its open root, and compares with Node.contains, which stops at the boundary: a recognizer on a control inside a moving sheet's shadow root gets no finger, and the tap's click goes to the host. Fails until the hit test descends into open roots and compares along the flat tree.
Where a moving element is drawn away from its layout box, the host hit-tests what is drawn under the finger with the document's elementFromPoint, which answers a shadow host for anything inside its open root. The hit test now descends through each open root's own elementFromPoint and compares with the movers along the flat tree, so a recognizer on a control inside a moving sheet's shadow root gets its finger and the tap clicks that control.
…nt lets go The host's touch-action rules sit in an adopted sheet or a style element per document or shadow root, and were never removed, so a page whose last recognizer detached kept them, against spec:AST-094 FR24 (nothing touch-related remains after the last instance unmounts). Fails until a root's rules go with its last marked element.
…th its last marked element The touch-action writer records which elements it marked in each document or shadow root, and when the last one is unmarked (its last recognizer detached) it removes that root's adopted sheet or style element, so nothing touch-related remains after the last instance unmounts (spec:AST-094 FR24). A later mark writes the rules again; an element marked in another root first lets go of the old one.
… the inline style it reads - The motion registry, the host's engine gate and the README name the presented hit test as an engine workaround (spec:AST-013 FR6) with its review condition (re-measure on each iOS release; remove it once iOS WebKit hit-tests a compositor-moved element where it is drawn) and its regression fixture. - While a curve runs the animator holds the value it starts from inline, so presentation.ts and the README no longer say the inline style holds the end.
…core/internal/touch Core ships the internal entry @astryxdesign/core/internal/touch, which re-exports the gesture primitives for Astryx's own packages, and the Lab-import guard fails a Lab import of core outside core's exports (architecture:react-component-runtime INV13). The README and the barrel's header named internal workspace imports and said nothing here is exported; they now name that entry.
…ill needs the element, and the listeners that cancel The release note, the stream's header, the host's comment and the README said the stream is one passive listener per event type and that a document-capture stopPropagation no longer starves the recognizers. Such a stop never hid a listener on the same node, and some subscriptions cancel. They now say what changed: an app's window-capture stopPropagation, or a document-capture stopImmediatePropagation registered before core, no longer hides a touch; a recognizer that must claim still needs the touch to reach its element; the gesture host's touchend, click and contextmenu cancel while a claimer is attached and the page hold's touchmove while it holds; and the stream stays ahead of an app's window-capture listener only until a type's listener is re-added. The README's list of blocking touchmove listeners gains the claimed touch's target and the document while a claiming element moves.
…default it moves
The {stylexOptions} form of astryxStylex now passes StyleX
treeshakeCompensation: true unless the caller sets it. spec:AST-017 FR3
asks the release note to name the old and new default and the option
that restores the old one; it now does.
…er and for the two stops the input stream fixes - A still finger with sub-pixel jitter over a scroll view keeps its tap and scrolls nothing (spec:AST-094 FR18–FR22). - A tap is still recognized under an app's window-capture stopPropagation, and under a document-capture stopImmediatePropagation registered before core's: the recognizers hear touches on the window's capture phase. Chromium and WebKit pass.
…s once Where nothing that claims is attached, the host's touchend listener is passive and cannot cancel the engine's own click after a retargeted tap clicks what is drawn. The shared click guard swallows that late click whatever is attached; this case pins it, as asked on meta#8293.
…esture host's subscriptions The gesture host subscribes no click: the one cancelling click subscriber is the click guard's, added at a window's first arm and kept. The release note, the stream's header and the gestures README listed click with the host's touchend and contextmenu; they now name the guard's own.
…tion constant From review on meta#8890: spec:AST-094 FR4 lists the 200 ms Reduce Motion cap among the touch motion roles and says a component never plays a literal. The release capped its clock at a literal 200 in pressFeedback.ts (REDUCED_MOTION_MAX_MS) and at a literal 200ms in the overlay's reduced release duration. Both now read the shared constant: the controller timing.reducedMotionCrossFadeMs, the stylesheet reducedMotionFadeConsts.duration. Both are 200 ms, so nothing on screen changes; the built stylesheet's cap is still 200 ms. Red first: a unit case that stubs timing.reducedMotionCrossFadeMs at 120 ms found the release still fading at 120 ms on the change before this one; it now ends there.
…Motion constant too The row press layer capped its release under reduced motion at a literal 200ms (CELL_RELEASE_REDUCED), the same cap the review on meta#8890 asked the overlay to read from the shared constant (spec:AST-094 FR4: the 200 ms Reduce Motion cap is a role, never a literal). It now reads reducedMotionFadeConsts.duration, as the overlay does; both are 200 ms, and the built stylesheet's cap is still 200 ms. A unit case holds every reduced-motion cap in the layer and the overlay to the constant. Red first: it found the layer's literal on the change before this one.
…or the Space key's 200 ms, a list row's 200 ms Measures each release on a touch-first device (component:Button FR15): the release animation's own duration, read when `fading` is written; `fading` lasting at least that clock; and `fading` going within a few frames of the animation's end. Frames, not ms, bound the end, so a loaded machine cannot fail it. Passes in Chromium and WebKit on this head, before the fold onto meta#8117's new head, and is kept through it as the proof that the fold changes no timing.
…#8890, meta#8360 045f666) into the press release The release's 250 ms for a finger on a control stays where it was, in the control press layer's CSS, and a mouse's or the Space key's press keeps 200 ms through its input marker. The controller's own profile clock goes with the profiles meta#8117 dropped: the release-end code from meta#8890 waits for the running release animation, so a finger's release still ends at 250 ms. This branch's own copy of that code gives way to meta#8890's.
…duce Motion constant meta#8077's FR4 check (interactionOverlay.cell.test.ts) asks every release cap in the overlay to read the Reduce Motion constant through a min(). This branch's reduced-motion arms used the constant directly; they now take min(<the release's clock>, the constant), so a control's 250 ms and a mouse's or the Space key's 200 ms both cap at 200 ms, as before. The build's reduced-motion check reads the cap.
… or press profile The selection hold and the press profiles are gone from the base, so the Item case and the shared checks' notes no longer mention a held arm, a cell marker or a profile clock; the checks themselves are unchanged.
…-base (main's current Switch record) Switch.spec.md takes main's current record as merged (it arrives through meta#8118) and re-applies this pull request's touch contract to it: the thumb circle, the thumb inside the track and UISwitch's gestures become FR13-FR15 after main's FR1-FR12; FR10 states the 44 px coarse-pointer hit area this pull request ships, closing its implementation gap and the matching Current-state bullet; FR9 and FR10 note the cross-fade under a finger with reduced motion; the motion roles, the accessibility contract's pointer-cancellation sentence, PR1's touch listeners, one verification row and the decision log come back on main's text. pressFeedback.ts and its test take meta#8118's file as published, with this pull request's own change re-applied on it: the drag-hold attribute a Switch's thumb sets, its early return in onPointerMove, and its test.
… and FR13 Main's current Switch record numbers the coarse-pointer hit area FR10 and this pull request's thumb circle FR13 (it was FR4 in the draft), so the comments and test titles that cite them follow. No behavior change.
There was a problem hiding this comment.
This pull request is on hold for an engineering owner's decision: it adds FR13–FR15 and a Motion roles table to the current component:Switch record and rewrites component:Button FR15, and Switch FR15's own click after a hold or a flipping drag, and its finger-only drag, depart from spec:AST-094 FR11 and FR43. 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) and amends AST-094 FR11 and FR43 on main, or says which requirement should change instead. Whatever that decision, these changes are still needed:
- Land the stacked pull requests first, so this one carries only the Switch change (38,560 authored lines today).
- Let the row's press release wait for the Switch's 250 ms press layers, which today end on the controller's 200 ms clock and snap off on a busy page (
utils/pressFeedback.ts:352-377). - Read the writing direction at each touch, so a drag after a runtime
dirchange moves the thumb the right way (useSwitchTouch.ts:306). - Land #8671 or carry its check, so
scripts/touch-support-bundle.test.mjspasses. - Add real iOS Safari and Android Chrome recordings, and justify the flagged Switch CSS.
[Automated review]
| this.detach(); | ||
| this.elements = elements; | ||
| this.measureGeometry(); | ||
| this.direction = isRtlElement(elements.surface) ? -1 : 1; |
There was a problem hiding this comment.
The direction is read once, at attach, and the attach effect re-runs only for isTouch or isDisabled (:1048-1068). After a page flips dir without remounting the switch, the thumb's CSS rest mirrors but every drag keeps the old sign (:645, :746, :752), so a drag toward the on end only rubber-bands and a drag away from it turns the switch on (design:bidirectionality DR13). Reading isRtlElement(elements.surface) in touchDown (:559-566), with a case that changes dir after mount, would fix it.
| // UIButton's measured release, the table's internal constant | ||
| // (`button.highlight.releaseMs`, mirrored for the stylesheet and | ||
| // drift-tested): touch motion stays internal (spec:AST-094 DEC-11). | ||
| default: `${controlPressConsts.controlReleaseMs}ms`, |
There was a problem hiding this comment.
This layer (and the thumb's at :567) releases over 250 ms, but the attribute it keys on lives on the row, and the shared controller ends the row's release on its 200 ms clock (PRESS_FADE_MS), waiting only for animations on the row itself or its own pseudo-elements, never a descendant's (utils/pressFeedback.ts:352-377). Under a coarse pointer the row has no animation of its own, so fading goes at 200 ms and both layers vanish mid-fade; when the lift's frame is busy they stay fully painted through the block and then disappear. The release-curve case freezes the page's timers before sampling (Switch.interaction.browser.spec.ts:790-795), so it cannot see this. Letting the row's release wait for these layers, plus a case that blocks the lift's frame with timers live, would cover it.
Depends on #8118 (which carries #8360) and #8123, merged in. Review only this PR's change: vjeux/astryx@c02-8124-base...uikit/c02-switch-touch
This push re-applies this PR's Switch rows on main's current Switch record (FR10, FR13–FR15), so it needs a fresh spec approval; it also merges #8118's fold onto #8360. This PR's code is unchanged apart from comments citing the new FR numbers.
Why
On a phone, Switch acts like a desktop checkbox: a finger can't drag its thumb (P3), and it freezes while the page runs scripts (P10).
What led to it
spec:AST-094(current) asks every control to feel native under a finger: follow it, keep moving while scripts run, and never take the page's scroll. Switch had only the browser checkbox. #8123 measured iOS's own switch for this one to match;component:SwitchFR10 and FR13–FR15 change here.Options and tradeoffs
translate,scale,opacityonly): follows the finger, keeps moving through a blocked main thread, and a vertical swipe still scrolls. It costs the shared touch layer (34.2 KB gzip), which every touch component reuses.Two behaviours picked a side:
spec:AST-020FR9 allows (P19).spec:AST-094FR11, FR20).Why this choice
Only option 3 meets AST-094's motion and scroll rules; its cost is shared, and the mouse, keyboard and screen-reader paths are unchanged. No API change: the thumb keeps its circle and size, and under a coarse pointer its hit area grows toward 44 px, stopping halfway to a neighbour (P17).
How to check it
Open Storybook's Switch › Default on a phone or the iOS simulator:
Switch.interaction.browser.spec.tsruns these with real touch input in Chromium and WebKit. The one red check, "is absent from the root entry", is the old bundle test that #8671 replaces.Evidence
iOS 26.2 simulator and Android 15 emulator: main, this PR, then iOS's own switch where shown; the red ring is the finger. P4 runs in Chromium; P11 uses a mouse. A3 and A8 show this PR only: main's takes drop frames.
Scope
Writing rubric results
packages/core/src/Switch/Switch.spec.md:spec:AST-094FR11 andspec:AST-020FR9.