Skip to content

fix(core): a checkbox or radio press releases on the compositor - #8233

Draft
vjeux wants to merge 1875 commits into
meta:mainfrom
vjeux:uikit/c06-indicator-press-layer
Draft

vjeux wants to merge 1875 commits into
meta:mainfrom
vjeux:uikit/c06-indicator-press-layer

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #8118 and #8219. Review only this PR's change: vjeux/astryx@a9fd510...63b0be3 (it also lists #8212's and #8219's commits).

Must land with #8077 and #8118: alone they drop the row indicators' press.

User impact

Under a finger, in the compositor press stack (#8118 with #8077's row layer):

  • A RadioList row paints the whole bare row, not its circle.
  • A CheckboxList row stops painting its box.
  • A standalone CheckboxInput's release runs on the main thread: held while script runs, then snaps.

Now box and circle paint their own press and fade on the compositor.

Evidence

iOS 26.2 Mobile Safari, a 0.4 s hold (magenta ring); the busy clip's counter stops while script blocks. Before: the stack without them.

Path Before After
CheckboxInput, a 700 ms block after the lift, ½ speed: frozen, then a snap before after
CheckboxList box: the row paints, the box doesn't before after
RadioList circle: the bare row paints instead before after

Problem and solution fit

  1. Why: the mark's layer read its host's --_press-paint-image (transparent under the cell layer) and faded through the host's main-thread strength.
  2. Why it harms: on rows the press vanished; standalone it froze.
  3. Why it matters: DR1 and AST-094 FR5.

The layer paints the pressed token at full strength, then releases by its own opacity on internal clocks (UIButton's 250 ms standalone, a cell's 200 ms in a row; under Reduce Motion the policy's fade, third commit). Every layer rule sits behind the coarse pointer: a fine pointer keeps main's behavior (fourth commit). RadioListItem drops the cell layer's ::before; its row stays bare. Each press owner declares its image and release, and each layer reads its nearest owner's, so a nested mark answers only its own (fifth commit).

Expected behavior and authority

  • design:user-states (current) DR1: CheckboxInput and RadioList indicators paint during a hold; in a RadioList row only the circle paints.
  • design:reduced-motion (current) DR2: under Reduce Motion a touch cross-fade is opacity only, ≤200 ms.
  • spec:AST-017 FR59, DEC-25: touch work leaves a fine pointer's behavior as it is. architecture:component-style-authoring INV4: a mark answers only its own owner.
  • spec:AST-094 FR5, FR11, DEC-11; button.highlight, list.highlight.
  • Proposed; pending owner review: UIKit highlights a whole option cell (U8); not done here.

Smallest restoration

Mouse :active, hover, pressed color and press ownership are unchanged; a test holds mouse pixels to the stack's.

Scope

  • One defect is restored.
  • Separable contradictory or unsettled tagalongs were removed or split.
  • No new public API, visual representation, default, or interaction model is hidden under the bug-fix label.
  • Public text and artifacts contain no internal Meta context.

Testing

@vercel

vercel Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 30, 2026 3:22am UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 24, 2026
@astracat-bot

astracat-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Oct 30, 2026, 3:49 AM UTC

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

Modified Components

Button (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 712B 712B 0B
Lines of Code 584 591 -
Complexity Very High Very High (83) -
CheckboxInput (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 478B 478B 0B
Lines of Code 355 403 -
Complexity Very High Very High (43) -
ClickableCard (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 427B 427B 0B
Lines of Code 229 231 -
Complexity High High (20) -
Item (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 284B 284B 0B
Lines of Code 1372 1378 -
Complexity Very High Very High (122) -
SelectableCard (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 108B 108B 0B
Lines of Code 305 307 -
Complexity Very High Very High (33) -
Thumbnail (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 164B 164B 0B
Lines of Code 355 357 -
Complexity Very High Very High (46) -
TreeList (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 648B 648B 0B
Lines of Code 872 879 -
Complexity Very High Very High (100) -

Component Size

Component Import before Import after Delta CSS before CSS after Delta
charts/Chart N/A 66.8KB N/A N/A 20B N/A
charts/ChartAxis N/A 7.0KB N/A N/A 112B N/A
charts/ChartLegend N/A 56.1KB N/A N/A 20B N/A
charts/ChartTooltip N/A 56.1KB N/A N/A 20B N/A
core/AlertDialog 57.0KB 60.2KB +3.2KB ⚠️ 12.4KB 13.2KB +805B
core/AppShell 58.0KB 60.9KB +2.9KB ⚠️ 11.6KB 12.4KB +811B
core/AspectRatio 3.7KB 4.1KB +374B 3.5KB 3.7KB +261B
core/Avatar 37.6KB 37.9KB +360B 6.3KB 6.5KB +234B
core/AvatarGroup 39.9KB 40.3KB +360B 6.4KB 6.7KB +230B
core/AvatarGroupOverflow 40.1KB 42.6KB +2.6KB ⚠️ 6.4KB 6.6KB +234B
core/AvatarStatusDot 7.1KB 7.4KB +380B 3.4KB 3.7KB +264B
core/Badge 3.9KB 4.3KB +383B 3.8KB 4.1KB +255B
core/Banner 48.0KB 51.1KB +3.2KB ⚠️ 9.9KB 10.7KB +807B
core/BaseTypeahead 54.5KB 57.6KB +3.1KB ⚠️ 9.9KB 10.7KB +816B
core/Blockquote 3.5KB 3.9KB +383B 3.4KB 3.6KB +263B
core/BottomSheet 33.9KB 34.3KB +381B 7.9KB 8.1KB +250B
core/BottomSheetSwitcher 14.8KB 15.2KB +377B 3.3KB 3.5KB +264B
core/BreadcrumbItem 66.5KB 70.0KB +3.5KB ⚠️ 11.0KB 12.0KB +1.0KB ⚠️
core/Breadcrumbs 22.6KB 22.9KB +370B 3.3KB 3.5KB +263B
core/Button 43.4KB 46.6KB +3.2KB ⚠️ 9.2KB 10.0KB +806B
core/ButtonGroup 8.4KB 8.8KB +388B 3.3KB 3.5KB +264B
core/Calendar 55.1KB 58.2KB +3.2KB ⚠️ 10.0KB 10.8KB +801B
core/Card 6.4KB 6.8KB +376B 6.3KB 6.6KB +267B
core/Carousel 50.4KB 53.5KB +3.2KB ⚠️ 10.2KB 11.0KB +805B
core/Center 4.2KB 4.5KB +384B 4.6KB 4.8KB +266B
core/ChatComposer 60.8KB 63.9KB +3.2KB ⚠️ 10.8KB 11.6KB +805B
core/ChatComposerDrawer 24.0KB 24.4KB +414B 4.5KB 4.8KB +254B
core/ChatComposerInput 55.9KB 59.1KB +3.2KB ⚠️ 10.1KB 10.9KB +808B
core/ChatComposerTokenElement 55.9KB 59.1KB +3.2KB ⚠️ 10.1KB 10.9KB +808B
core/ChatDictationButton 46.6KB 49.8KB +3.2KB ⚠️ 9.5KB 10.3KB +805B
core/ChatLayout 49.0KB 52.2KB +3.2KB ⚠️ 9.9KB 10.7KB +803B
core/ChatLayoutScrollButton 46.5KB 49.7KB +3.2KB ⚠️ 9.6KB 10.3KB +805B
core/ChatMessage 23.2KB 23.6KB +382B 3.5KB 3.8KB +261B
core/ChatMessageBubble 7.0KB 7.4KB +379B 3.6KB 3.9KB +262B
core/ChatMessageList 29.1KB 29.4KB +381B 7.8KB 8.0KB +238B
core/ChatMessageMetadata 26.0KB 26.3KB +363B 3.9KB 4.1KB +246B
core/ChatSendButton 46.0KB 49.1KB +3.2KB ⚠️ 9.2KB 10.0KB +804B
core/ChatSystemMessage 6.7KB 7.1KB +386B 3.7KB 3.9KB +261B
core/ChatTokenizedText 6.9KB 7.3KB +370B 3.9KB 4.1KB +255B
core/ChatToolCalls 32.0KB 32.4KB +371B 8.9KB 9.1KB +239B
core/CheckIndicator 9.0KB 9.4KB +384B 3.6KB 3.8KB +261B
core/CheckboxIndicator 4.1KB 4.5KB +385B 3.8KB 4.0KB +241B
core/CheckboxInput 50.1KB 52.5KB +2.5KB ⚠️ 9.3KB 9.8KB +468B
core/CheckboxList 46.2KB 46.5KB +371B 8.1KB 8.4KB +237B
core/CheckboxListItem 54.2KB 57.8KB +3.5KB ⚠️ 10.5KB 11.4KB +977B
core/Citation 26.1KB 26.4KB +388B 4.1KB 4.4KB +262B
core/ClickableCard 12.8KB 15.8KB +3.0KB ⚠️ 6.9KB 7.7KB +813B
core/Code 3.5KB 3.9KB +383B 3.4KB 3.6KB +267B
core/CodeBlock 60.7KB 63.8KB +3.1KB ⚠️ 10.7KB 11.5KB +819B
core/Collapsible 11.7KB 14.3KB +2.5KB ⚠️ 4.2KB 4.5KB +260B
core/CollapsibleGroup 6.4KB 6.7KB +375B 3.2KB 3.4KB +263B
core/CommandPalette 51.0KB 53.7KB +2.7KB ⚠️ 11.8KB 12.1KB +236B
core/CommandPaletteEmpty 6.1KB 6.5KB +372B 3.3KB 3.6KB +265B
core/CommandPaletteFooter 23.4KB 23.8KB +391B 3.5KB 3.8KB +261B
core/CommandPaletteGroup 3.5KB 3.8KB +378B 3.3KB 3.6KB +262B
core/CommandPaletteInput 32.5KB 32.9KB +396B 8.1KB 8.4KB +244B
core/CommandPaletteItem 8.4KB 11.0KB +2.5KB ⚠️ 3.5KB 3.7KB +264B
core/CommandPaletteList 22.6KB 22.9KB +378B 3.3KB 3.5KB +265B
core/ComplexSelector 56.0KB 59.2KB +3.2KB ⚠️ 10.6KB 11.4KB +799B
core/ContextMenu 74.7KB 78.5KB +3.8KB ⚠️ 14.6KB 16.0KB +1.4KB ⚠️
core/ContextMenuItem 53.0KB 56.1KB +3.1KB ⚠️ 9.6KB 10.4KB +805B
core/DateInput 99.7KB 103.0KB +3.3KB ⚠️ 14.8KB 15.6KB +807B
core/DateRangeInput 69.5KB 72.8KB +3.3KB ⚠️ 11.2KB 12.0KB +794B
core/DateTimeInput 109.8KB 113.1KB +3.3KB ⚠️ 15.2KB 16.0KB +818B
core/Dialog 16.2KB 16.6KB +390B 7.1KB 7.3KB +224B
core/DialogHeader 49.0KB 52.2KB +3.2KB ⚠️ 9.8KB 10.6KB +807B
core/Divider 6.5KB 6.8KB +375B 3.6KB 3.8KB +264B
core/Drawer 31.6KB 32.0KB +415B 8.0KB 8.3KB +241B
core/DrawerHeader 49.1KB 52.3KB +3.2KB ⚠️ 11.2KB 12.0KB +806B
core/DropdownMenu 72.8KB 76.4KB +3.5KB ⚠️ 14.7KB 16.2KB +1.4KB ⚠️
core/DropdownMenuCheckboxItem 16.1KB 19.2KB +3.0KB ⚠️ 5.6KB 6.2KB +637B
core/DropdownMenuDivider 6.6KB 7.0KB +388B 3.6KB 3.8KB +263B
core/DropdownMenuGroup 6.1KB 6.4KB +381B 3.3KB 3.5KB +266B
core/DropdownMenuItem 15.4KB 18.4KB +3.0KB ⚠️ 5.2KB 5.8KB +657B
core/DropdownMenuRadioGroup 6.0KB 6.4KB +372B 3.2KB 3.5KB +264B
core/DropdownMenuRadioItem 16.2KB 19.2KB +3.0KB ⚠️ 5.6KB 6.2KB +637B
core/DropdownMenuSubMenu 58.2KB 61.2KB +3.0KB ⚠️ 9.5KB 10.1KB +631B
core/EmptyState 7.7KB 8.1KB +376B 3.5KB 3.8KB +263B
core/Field 44.4KB 44.7KB +366B 8.0KB 8.2KB +242B
core/FieldLabel 39.9KB 40.3KB +377B 6.5KB 6.8KB +234B
core/FieldStatus 10.4KB 10.8KB +380B 4.2KB 4.5KB +244B
core/FileInput 54.3KB 57.5KB +3.2KB ⚠️ 10.3KB 11.1KB +795B
core/FormLayout 6.3KB 6.7KB +386B 3.4KB 3.6KB +261B
core/Grid 4.3KB 4.7KB +388B 3.7KB 3.9KB +264B
core/GridSpan 3.4KB 3.8KB +383B 3.2KB 3.5KB +264B
core/HStack 7.3KB 7.7KB +392B 4.9KB 5.2KB +267B
core/Heading 9.1KB 9.5KB +381B 6.8KB 7.0KB +234B
core/Icon 8.8KB 9.2KB +371B 3.6KB 3.8KB +261B
core/IconButton 43.5KB 46.6KB +3.2KB ⚠️ 9.2KB 10.0KB +806B
core/InputGroup 45.0KB 45.4KB +392B 8.0KB 8.3KB +237B
core/InputGroupText 6.3KB 6.7KB +372B 3.7KB 4.0KB +261B
core/Item 11.4KB 14.6KB +3.3KB ⚠️ 4.7KB 5.3KB +659B
core/Kbd 6.7KB 7.0KB +376B 3.5KB 3.7KB +262B
core/LayerProvider 63.4KB 66.5KB +3.1KB ⚠️ 10.1KB 10.9KB +812B
core/Layout 8.1KB 8.5KB +397B 5.5KB 5.8KB +266B
core/LayoutContent 7.4KB 7.8KB +383B 4.9KB 5.2KB +268B
core/LayoutFooter 7.3KB 7.7KB +385B 4.8KB 5.1KB +267B
core/LayoutHeader 7.4KB 7.8KB +386B 4.8KB 5.1KB +268B
core/LayoutPanel 7.6KB 7.9KB +391B 4.8KB 5.1KB +267B
core/Lightbox 51.3KB 54.5KB +3.2KB ⚠️ 9.8KB 10.6KB +808B
core/Link 43.7KB 46.1KB +2.4KB ⚠️ 7.6KB 7.8KB +237B
core/List 6.7KB 7.1KB +384B 3.4KB 3.7KB +260B
core/ListItem 53.7KB 57.0KB +3.3KB ⚠️ 10.5KB 11.4KB +977B
core/Markdown 144.5KB 148.2KB +3.7KB ⚠️ 17.4KB 18.7KB +1.4KB ⚠️
core/MetadataList 23.3KB 23.7KB +380B 3.6KB 3.9KB +265B
core/MetadataListItem 6.5KB 6.8KB +380B 3.4KB 3.7KB +263B
core/MobileNav 51.2KB 54.3KB +3.1KB ⚠️ 10.1KB 10.8KB +809B
core/MobileNavToggle 46.4KB 49.5KB +3.2KB ⚠️ 9.4KB 10.2KB +803B
core/MoreMenu 73.2KB 76.6KB +3.4KB ⚠️ 14.7KB 16.2KB +1.4KB ⚠️
core/MultiSelector 73.1KB 76.3KB +3.3KB ⚠️ 14.6KB 15.6KB +1015B
core/NavHeadingMenu 8.9KB 9.3KB +388B 3.2KB 3.5KB +264B
core/NavIcon 3.4KB 3.8KB +381B 3.3KB 3.6KB +266B
core/NumberInput 55.9KB 59.1KB +3.2KB ⚠️ 10.7KB 11.5KB +805B
core/Outline 28.8KB 31.3KB +2.5KB ⚠️ 4.0KB 4.3KB +261B
core/OverflowList 8.5KB 8.9KB +385B 3.5KB 3.8KB +260B
core/Overlay 19.5KB 19.9KB +341B 3.8KB 4.1KB +239B
core/Pagination 84.9KB 88.3KB +3.3KB ⚠️ 15.3KB 16.4KB +1.0KB ⚠️
core/Popover 50.0KB 53.0KB +2.9KB ⚠️ 12.1KB 12.9KB +797B
core/PowerSearch 158.2KB 161.6KB +3.4KB ⚠️ 19.9KB 20.9KB +1.0KB ⚠️
core/ProgressBar 7.7KB 8.1KB +366B 6.6KB 6.8KB +232B
core/RadioIndicator 3.8KB 4.2KB +385B 3.7KB 3.9KB +240B
core/RadioList 45.8KB 46.1KB +391B 8.0KB 8.2KB +240B
core/RadioListItem 52.4KB 55.5KB +3.2KB ⚠️ 9.7KB 10.5KB +803B
core/ResizeHandle 25.2KB 25.6KB +411B 3.9KB 4.1KB +261B
core/Section 7.5KB 7.9KB +396B 6.2KB 6.4KB +267B
core/SegmentedControl 38.9KB 39.3KB +407B 6.3KB 6.6KB +234B
core/SegmentedControlItem 8.6KB 11.2KB +2.6KB ⚠️ 3.9KB 4.1KB +257B
core/SelectableCard 12.8KB 15.8KB +3.0KB ⚠️ 7.1KB 7.9KB +813B
core/Selector 76.5KB 79.9KB +3.4KB ⚠️ 15.0KB 16.0KB +1.0KB ⚠️
core/SelectorOption 14.5KB 17.7KB +3.2KB ⚠️ 5.1KB 5.7KB +656B
core/SideNav 58.1KB 61.3KB +3.2KB ⚠️ 10.6KB 11.3KB +809B
core/SideNavCollapseButton 46.8KB 50.0KB +3.2KB ⚠️ 9.5KB 10.2KB +802B
core/SideNavHeading 54.9KB 58.0KB +3.1KB ⚠️ 10.1KB 10.9KB +809B
core/SideNavItem 54.6KB 57.8KB +3.2KB ⚠️ 10.1KB 10.9KB +812B
core/SideNavSection 6.5KB 6.9KB +366B 3.6KB 3.9KB +262B
core/Skeleton 3.8KB 4.1KB +374B 3.6KB 3.9KB +238B
core/Slider 48.6KB 49.0KB +390B 8.5KB 8.8KB +237B
core/Spinner 26.4KB 26.8KB +399B 7.5KB 7.8KB +235B
core/Stack 7.1KB 7.4KB +376B 4.9KB 5.2KB +267B
core/StackItem 6.0KB 6.4KB +372B 3.3KB 3.6KB +264B
core/StatusDot 19.0KB 19.3KB +334B 6.2KB 6.4KB +233B
core/Step 32.6KB 35.1KB +2.5KB ⚠️ 5.3KB 5.6KB +252B
core/Stepper 47.5KB 50.7KB +3.2KB ⚠️ 9.6KB 10.3KB +803B
core/Switch 48.5KB 51.1KB +2.6KB ⚠️ 9.3KB 9.6KB +234B
core/Tab 9.7KB 12.3KB +2.6KB ⚠️ 4.1KB 4.3KB +253B
core/TabList 40.3KB 40.7KB +378B 7.2KB 7.4KB +244B
core/TabMenu 51.8KB 54.9KB +3.2KB ⚠️ 10.0KB 10.8KB +801B
core/Table 83.9KB 87.6KB +3.7KB ⚠️ 15.1KB 16.5KB +1.4KB ⚠️
core/TableBody 3.3KB 3.6KB +375B 3.2KB 3.4KB +264B
core/TableCell 75.8KB 79.5KB +3.7KB ⚠️ 14.8KB 16.3KB +1.4KB ⚠️
core/TableFooter 3.3KB 3.7KB +375B 3.2KB 3.4KB +264B
core/TableHeader 3.3KB 3.7KB +375B 3.2KB 3.4KB +264B
core/TableHeaderCell 75.5KB 79.3KB +3.8KB ⚠️ 14.8KB 16.2KB +1.4KB ⚠️
core/TableRow 6.2KB 6.6KB +380B 3.5KB 3.8KB +259B
core/TableSelectionToolbar 52.5KB 55.7KB +3.2KB ⚠️ 11.2KB 12.0KB +812B
core/Text 8.6KB 9.0KB +385B 6.8KB 7.0KB +234B
core/TextArea 49.6KB 49.9KB +384B 9.4KB 9.6KB +237B
core/TextInput 53.7KB 56.9KB +3.2KB ⚠️ 10.5KB 11.3KB +808B
core/Thumbnail 50.8KB 54.0KB +3.2KB ⚠️ 11.0KB 11.8KB +800B
core/TimeInput 86.9KB 90.1KB +3.2KB ⚠️ 13.8KB 14.5KB +799B
core/Timer 9.2KB 9.6KB +396B 6.8KB 7.0KB +234B
core/Timestamp 27.7KB 28.1KB +486B 9.5KB 10.3KB +804B
core/Toast 60.0KB 63.2KB +3.2KB ⚠️ 9.7KB 10.5KB +806B
core/ToggleButton 44.3KB 47.4KB +3.1KB ⚠️ 9.3KB 10.1KB +806B
core/ToggleButtonGroup 6.3KB 6.7KB +385B 3.2KB 3.5KB +265B
core/Token 29.7KB 32.3KB +2.5KB ⚠️ 4.5KB 4.7KB +262B
core/Tokenizer 68.8KB 71.6KB +2.9KB ⚠️ 16.5KB 17.3KB +804B
core/Toolbar 39.9KB 40.3KB +401B 7.7KB 8.0KB +273B
core/TopNav 52.0KB 55.3KB +3.3KB ⚠️ 10.4KB 11.2KB +819B
core/TopNavHeading 54.3KB 57.4KB +3.1KB ⚠️ 9.9KB 10.7KB +816B
core/TopNavItem 9.0KB 11.6KB +2.6KB ⚠️ 3.8KB 4.1KB +261B
core/TopNavMegaMenu 54.3KB 57.4KB +3.1KB ⚠️ 10.8KB 11.6KB +810B
core/TopNavMegaMenuFeaturedCard 6.7KB 7.1KB +381B 3.4KB 3.7KB +261B
core/TopNavMegaMenuItem 9.1KB 11.7KB +2.6KB ⚠️ 4.1KB 4.3KB +261B
core/TopNavMenu 53.7KB 56.8KB +3.1KB ⚠️ 10.1KB 10.9KB +812B
core/TreeList 32.7KB 35.8KB +3.1KB ⚠️ 4.7KB 5.3KB +657B
core/Typeahead 61.6KB 64.8KB +3.2KB ⚠️ 11.3KB 12.1KB +800B
core/TypeaheadItem 6.2KB 6.6KB +372B 3.4KB 3.7KB +262B
core/VStack 7.3KB 7.7KB +391B 4.9KB 5.2KB +267B
lab/ChartDotGL N/A 7.7KB N/A N/A 20B N/A
lab/ChartDotGLInteractive N/A 8.6KB N/A N/A 20B N/A
lab/ChartHeatmapGL N/A 8.8KB N/A N/A 20B N/A
lab/ChartStreamGL N/A 7.7KB N/A N/A 20B N/A
lab/ChatEmojiPicker N/A 68.2KB N/A N/A 773B N/A
lab/ChatReactionBar N/A 68.7KB N/A N/A 1.1KB N/A
lab/ChatReasoning N/A 7.3KB N/A N/A 967B N/A
lab/ChatTypingIndicator N/A 24.1KB N/A N/A 612B N/A
lab/ChatUnreadDivider N/A 3.9KB N/A N/A 348B N/A
lab/CircularProgress N/A 7.5KB N/A N/A 1.0KB N/A
lab/CodeEditor N/A 15.0KB N/A N/A 665B N/A
lab/CommentComposer N/A 97.1KB N/A N/A 1.9KB N/A
lab/CommentPin N/A 50.1KB N/A N/A 1.2KB N/A
lab/CommentThread N/A 219.9KB N/A N/A 2.4KB N/A
lab/Comments N/A 224.4KB N/A N/A 2.5KB N/A
lab/DiffViewer N/A 48.3KB N/A N/A 2.1KB N/A
lab/EmojiPicker N/A 66.3KB N/A N/A 722B N/A
lab/InfoTip N/A 22.0KB N/A N/A 458B N/A
lab/ListInput N/A 78.2KB N/A N/A 1.6KB N/A
lab/LogStream N/A 8.3KB N/A N/A 1.6KB N/A
lab/MobilePowerSearch N/A 162.3KB N/A N/A 1.4KB N/A
lab/MobileTokenizer N/A 94.3KB N/A N/A 775B N/A
lab/SVGIcon N/A 7.9KB N/A N/A 764B N/A
lab/Schedule N/A 63.0KB N/A N/A 3.9KB N/A
lab/Stat N/A 4.4KB N/A N/A 646B N/A
lab/ThreeDScatterGL N/A 7.9KB N/A N/A 20B N/A
lab/TourStep N/A 58.4KB N/A N/A 590B N/A
lab/TransferList N/A 70.2KB N/A N/A 1.7KB N/A
lab/TransferListSelector N/A 76.0KB N/A N/A 1.9KB N/A
richtext/RichTextEditor N/A 274.5KB N/A N/A 2.6KB N/A
richtext/RichTextEditorToolbar N/A 216.0KB N/A N/A 454B N/A
richtext/RichTextView N/A 254.9KB N/A N/A 2.3KB N/A

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.

⚠️ Touch-only size budget

These components grew past the touch-only budget (2 KB gzip JavaScript or 1 KB gzip CSS per component). If the growth runs only on a coarse pointer, load its JavaScript lazily so the import does not carry it, or justify the cost in the pull request description; CSS stays in the package stylesheet, so justify it.

Component JavaScript CSS
core/AlertDialog +3.2KB ⚠️ +805B
core/AppShell +2.9KB ⚠️ +811B
core/AvatarGroupOverflow +2.6KB ⚠️ +234B
core/Banner +3.2KB ⚠️ +807B
core/BaseTypeahead +3.1KB ⚠️ +816B
core/BreadcrumbItem +3.5KB ⚠️ +1.0KB ⚠️
core/Button +3.2KB ⚠️ +806B
core/Calendar +3.2KB ⚠️ +801B
core/Carousel +3.2KB ⚠️ +805B
core/ChatComposer +3.2KB ⚠️ +805B
core/ChatComposerInput +3.2KB ⚠️ +808B
core/ChatComposerTokenElement +3.2KB ⚠️ +808B
core/ChatDictationButton +3.2KB ⚠️ +805B
core/ChatLayout +3.2KB ⚠️ +803B
core/ChatLayoutScrollButton +3.2KB ⚠️ +805B
core/ChatSendButton +3.2KB ⚠️ +804B
core/CheckboxInput +2.5KB ⚠️ +468B
core/CheckboxListItem +3.5KB ⚠️ +977B
core/ClickableCard +3.0KB ⚠️ +813B
core/CodeBlock +3.1KB ⚠️ +819B
core/Collapsible +2.5KB ⚠️ +260B
core/CommandPalette +2.7KB ⚠️ +236B
core/CommandPaletteItem +2.5KB ⚠️ +264B
core/ComplexSelector +3.2KB ⚠️ +799B
core/ContextMenu +3.8KB ⚠️ +1.4KB ⚠️
core/ContextMenuItem +3.1KB ⚠️ +805B
core/DateInput +3.3KB ⚠️ +807B
core/DateRangeInput +3.3KB ⚠️ +794B
core/DateTimeInput +3.3KB ⚠️ +818B
core/DialogHeader +3.2KB ⚠️ +807B
core/DrawerHeader +3.2KB ⚠️ +806B
core/DropdownMenu +3.5KB ⚠️ +1.4KB ⚠️
core/DropdownMenuCheckboxItem +3.0KB ⚠️ +637B
core/DropdownMenuItem +3.0KB ⚠️ +657B
core/DropdownMenuRadioItem +3.0KB ⚠️ +637B
core/DropdownMenuSubMenu +3.0KB ⚠️ +631B
core/FileInput +3.2KB ⚠️ +795B
core/IconButton +3.2KB ⚠️ +806B
core/Item +3.3KB ⚠️ +659B
core/LayerProvider +3.1KB ⚠️ +812B
core/Lightbox +3.2KB ⚠️ +808B
core/Link +2.4KB ⚠️ +237B
core/ListItem +3.3KB ⚠️ +977B
core/Markdown +3.7KB ⚠️ +1.4KB ⚠️
core/MobileNav +3.1KB ⚠️ +809B
core/MobileNavToggle +3.2KB ⚠️ +803B
core/MoreMenu +3.4KB ⚠️ +1.4KB ⚠️
core/MultiSelector +3.3KB ⚠️ +1015B
core/NumberInput +3.2KB ⚠️ +805B
core/Outline +2.5KB ⚠️ +261B
core/Pagination +3.3KB ⚠️ +1.0KB ⚠️
core/Popover +2.9KB ⚠️ +797B
core/PowerSearch +3.4KB ⚠️ +1.0KB ⚠️
core/RadioListItem +3.2KB ⚠️ +803B
core/SegmentedControlItem +2.6KB ⚠️ +257B
core/SelectableCard +3.0KB ⚠️ +813B
core/Selector +3.4KB ⚠️ +1.0KB ⚠️
core/SelectorOption +3.2KB ⚠️ +656B
core/SideNav +3.2KB ⚠️ +809B
core/SideNavCollapseButton +3.2KB ⚠️ +802B
core/SideNavHeading +3.1KB ⚠️ +809B
core/SideNavItem +3.2KB ⚠️ +812B
core/Step +2.5KB ⚠️ +252B
core/Stepper +3.2KB ⚠️ +803B
core/Switch +2.6KB ⚠️ +234B
core/Tab +2.6KB ⚠️ +253B
core/TabMenu +3.2KB ⚠️ +801B
core/Table +3.7KB ⚠️ +1.4KB ⚠️
core/TableCell +3.7KB ⚠️ +1.4KB ⚠️
core/TableHeaderCell +3.8KB ⚠️ +1.4KB ⚠️
core/TableSelectionToolbar +3.2KB ⚠️ +812B
core/TextInput +3.2KB ⚠️ +808B
core/Thumbnail +3.2KB ⚠️ +800B
core/TimeInput +3.2KB ⚠️ +799B
core/Toast +3.2KB ⚠️ +806B
core/ToggleButton +3.1KB ⚠️ +806B
core/Token +2.5KB ⚠️ +262B
core/Tokenizer +2.9KB ⚠️ +804B
core/TopNav +3.3KB ⚠️ +819B
core/TopNavHeading +3.1KB ⚠️ +816B
core/TopNavItem +2.6KB ⚠️ +261B
core/TopNavMegaMenu +3.1KB ⚠️ +810B
core/TopNavMegaMenuItem +2.6KB ⚠️ +261B
core/TopNavMenu +3.1KB ⚠️ +812B
core/TreeList +3.1KB ⚠️ +657B
core/Typeahead +3.2KB ⚠️ +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.0KB +1.4KB

Accessibility Audit

Status: 8 accessibility violation(s) found — 8 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)
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 2/28 stories · Learn more
    • WCAG: 1.4.3 (Level AA)
CheckboxList - 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/23 stories · Learn more
    • WCAG: 1.4.3 (Level AA)
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)
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)
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/16 stories · Learn more
    • WCAG: 1.4.3 (Level AA)
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)
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)

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

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an owner decision about draft spec:AST-094. Its FR1–FR23 and DEC-1/DEC-2 are new requirements, it says it amends the current design:user-states DR1/DR3, family:buttons, architecture:theme-tokens and spec:AST-025 FR14 and extends architecture:interaction-modality, and holdSelection, divider hiding and the ./utils/pressLayer.stylex subpath still need a governing spec identified. 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 the amended current records need updating for the intended changes. Independently of that decision:

  • Land the stacked predecessors first: this diff carries 30,836 authored lines across 36 commits for an 861-line fix.
  • Correct .changeset/gesture-press-tracking.md and .changeset/gesture-host.md, which say no component uses code that every usePressFeedback surface now runs.
  • Name the external system in the useGestureRecognizer.ts Effect that calls setEnabled, which can run the consumer's onGesture.

[Automated review]

Comment thread .changeset/gesture-press-tracking.md Outdated
'@astryxdesign/core': patch
---

[chore] Internal: UIControl press tracking with control and cell profiles, the shared press paint, delegated press tracking and the gesture hooks. Not exported from the package entry point; no component uses them yet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this head usePressFeedback installs usePressAttribute, which builds on the gesture host and PressTracker (gestures/delegatedPress.ts:135, :177), so every Astryx pressable uses this code; "no component uses them yet" no longer holds.

Comment thread .changeset/gesture-host.md Outdated
'@astryxdesign/core': patch
---

[chore] Internal: the gesture system's DOM host (claims, click suppression, the browser's takeover, the pointer behind a contact). Not exported from the package entry point; no component uses it yet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every usePressFeedback surface now mounts the gesture host through the delegated press tracking, so "no component uses it yet" misstates the release.

}, [ref, recognizer, manageStyle]);

useIsomorphicLayoutEffect(() => {
recognizer.setEnabled(isEnabled);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setEnabled(false) on an active recognizer cancels it and runs the consumer's onGesture from inside this layout Effect (GestureRecognizer.ts:548-552, :582-585). Per architecture:react-component-runtime INV3, name the recognizer as the external system this Effect synchronizes, or cancel from the code path that disables it.

Comment thread packages/core/src/utils/index.ts Outdated
@@ -118,3 +118,15 @@ export {isImeKeyEvent} from './ime';
// that paints and composes one of these on it; the controller, its attributes
// and its clocks are internals.
export {interactionOverlayStyles} from './interactionOverlay.stylex';

// A list row kept selected while a navigation covers it, deselected as the
// navigation pops (UIKit's push-from-a-row). Exported for @astryxdesign/lab's

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No package in the repository calls holdSelection at this head, so the comment's "which calls it" describes a caller that does not exist yet.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's decision on draft spec:AST-094 (FR1-FR23, DEC-1, DEC-2), which the press profiles, gesture host, adaptive boundary and new public exports here rest on; an engineering owner records it by commenting /approve-spec b7a50775c707326c4b854849d044ae84da8ad700 on this pull request, and the author may do so as an engineering owner. Whatever that decision, two changes are needed:

  • Land #8077, #8118 and #8219 separately so this pull request carries only its 945-line indicator fix instead of 30,920 authored lines.
  • Correct the gesture-host and press-tracking changesets (every pressable now runs that code), the holdSelection export comment and FR11's cell delay, add a release note for ./utils/pressLayer.stylex, and give the Effect at gestures/useGestureRecognizer.ts:81-83 the comment naming the system it synchronizes.

[Automated review]

Comment thread .changeset/gesture-host.md Outdated
'@astryxdesign/core': patch
---

[chore] Internal: the gesture system's DOM host (claims, click suppression, the browser's takeover, the pointer behind a contact). Not exported from the package entry point; no component uses it yet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every pressable now runs the gesture host: usePressFeedback calls usePressAttribute, which builds on getGestureHost (gestures/delegatedPress.ts:135). "No component uses it yet" tells consumers to expect no runtime change.

Comment thread .changeset/gesture-press-tracking.md Outdated
'@astryxdesign/core': patch
---

[chore] Internal: UIControl press tracking with control and cell profiles, the shared press paint, delegated press tracking and the gesture hooks. Not exported from the package entry point; no component uses them yet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

installPressAttribute creates a PressTracker for every touch on a pressable (gestures/delegatedPress.ts:177), so "no component uses them yet" is not what ships.

}, [ref, recognizer, manageStyle]);

useIsomorphicLayoutEffect(() => {
recognizer.setEnabled(isEnabled);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turning isEnabled off mid-gesture cancels the recognizer here and runs the consumer's onGesture inside this layout Effect. architecture:react-component-runtime INV3 asks the Effect to name the external system it synchronizes; a one-line comment naming the gesture recognizer would do.

Comment thread packages/core/src/utils/index.ts Outdated
@@ -118,3 +118,15 @@ export {isImeKeyEvent} from './ime';
// that paints and composes one of these on it; the controller, its attributes
// and its clocks are internals.
export {interactionOverlayStyles} from './interactionOverlay.stylex';

// A list row kept selected while a navigation covers it, deselected as the
// navigation pops (UIKit's push-from-a-row). Exported for @astryxdesign/lab's

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing in packages/lab calls holdSelection at this head; the comment says it does.

views, keeps tracking within its expanded bounds (70 pt), turns its highlight
off and on as the finger drags out and back, and activates on a lift inside.
A cell (a table or collection cell) highlights 150 ms after touch-down inside
a scroll view and drops the highlight when the scroll wins. The 150 ms delay

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cell profile waits 150 ms wherever the row sits (gestures/PressTracker.ts:129, highlightDelay: 'always'), and .changeset/press-control-profile.md promises the same, so this sentence describes a press no list row has.

@vjeux
vjeux force-pushed the uikit/c06-indicator-press-layer branch from b7a5077 to 34a91bb Compare October 24, 2026 22:52
@cixzhang

Copy link
Copy Markdown
Contributor

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.

@vjeux

vjeux commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Agreed: one implementation.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's decision on draft spec:AST-094 (FR1-FR23, DEC-1, DEC-2), which the press profiles, gesture host and new public exports here rest on; an engineering owner records it 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 needed:

  • Land #8077, #8118 and #8219 separately so this pull request carries only its 1,038-line indicator fix instead of 33,015 authored lines.
  • Give Button and card press releases the 200 ms Reduce Motion cap the checkbox now has (design:reduced-motion DR2), and drop holdSelection's reducedMotion: 'keep' (DR4).
  • Resolve the adaptive compact rule from spec:AST-012 FR9's definition (below the effective md width or under 500 px tall) instead of a 744 px width of its own.
  • Correct the gesture-host and press-tracking changesets, the holdSelection export comment and FR11's cell delay, and name the external system in the Effect at gestures/useGestureRecognizer.ts:81-83.

[Automated review]

default: null,
[ENABLED]: {default: null, [PRESSED_FADING]: release},
},
animationDuration: {default: null, [PRESSED_FADING]: CONTROL_RELEASE},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Under Reduce Motion with a coarse pointer this release still runs 250 ms: a probe at this head reads 250 ms on Button's ::before and on ClickableCard's ::after (controlOver, :222), where the base ran 200 ms. design:reduced-motion DR2 caps it at an opacity change of 200 ms or less; the @media (prefers-reduced-motion: reduce) arm CheckboxInput.tsx:158 adds would fix both layers.

export const TOUCH_QUERY = '(pointer: coarse)';

/** The viewport is compact: layout behaviours. */
export const COMPACT_QUERY = '(width < 744px), (height < 500px)';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spec:AST-012 FR9 (current) 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" (architecture:public-component-api INV17 agrees). With 744 px, a coarse screen 744-767 px wide loses the bottom sheet FR9 gives it, and a theme's md override is ignored.

? {}
: {velocity: {opacity: -timing.velocity}}),
},
{at, reducedMotion: 'keep'},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reducedMotion: 'keep' lets a caller's spring (and, at :293, the 350 ms in-transition deselect) run unchanged under Reduce Motion. holdSelection is public, so this is the opt-out design:reduced-motion DR4 rules out; under reduced motion the deselect should be an opacity change of 200 ms or less on a coarse pointer (DR2) and immediate otherwise (DR3).

Comment thread .changeset/gesture-host.md Outdated
'@astryxdesign/core': patch
---

[chore] Internal: the gesture system's DOM host (claims, click suppression, the browser's takeover, the pointer behind a contact). Not exported from the package entry point; no component uses it yet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every pressable now runs the gesture host: usePressFeedback calls usePressAttribute (hooks/usePressFeedback.ts:82), which builds on getGestureHost (gestures/delegatedPress.ts:135). "No component uses it yet" tells consumers to expect no runtime change.

Comment thread .changeset/gesture-press-tracking.md Outdated
'@astryxdesign/core': patch
---

[chore] Internal: UIControl press tracking with control and cell profiles, the shared press paint, delegated press tracking and the gesture hooks. Not exported from the package entry point; no component uses them yet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

installPressAttribute creates a PressTracker for every touch on a pressable (gestures/delegatedPress.ts:177), so "no component uses them yet" is not what ships.

Comment thread packages/core/src/utils/index.ts Outdated
Comment on lines +122 to +124
// A list row kept selected while a navigation covers it, deselected as the
// navigation pops (UIKit's push-from-a-row). Exported for @astryxdesign/lab's
// navigation stack, which calls it with the activation that pushed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No package in the repository calls holdSelection at this head, so "which calls it" describes a caller that does not exist yet.

}, [ref, recognizer, manageStyle]);

useIsomorphicLayoutEffect(() => {
recognizer.setEnabled(isEnabled);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turning isEnabled off mid-gesture cancels the recognizer here (GestureRecognizer.ts:548-553) and runs the consumer's onGesture inside this layout Effect. architecture:react-component-runtime INV3 asks the Effect to name the external system it synchronizes; a one-line comment naming the gesture recognizer would do, or cancel from the code path that disables it.

Comment on lines +135 to +137
A cell (a table or collection cell) highlights 150 ms after touch-down inside
a scroll view and drops the highlight when the scroll wins. The 150 ms delay
applies only inside scroll views. **Proposed; pending owner review:** this

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cell profile waits 150 ms wherever the row sits (gestures/PressTracker.ts:129, highlightDelay: 'always'), and .changeset/press-control-profile.md promises the same, so this sentence describes a press no list row has.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's decision on draft spec:AST-094 (FR1–FR23, DEC-1, DEC-2), which the press profiles, gesture host and new public exports here rest on; an engineering owner records it 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 needed:

  • Land #8077, #8118 and #8219 separately so this pull request carries only its 1,101-line indicator fix instead of 33,172 authored lines.
  • Give Button and card press releases the 200 ms Reduce Motion cap the checkbox now has (design:reduced-motion DR2), and stop holdSelection playing its 350 ms deselect or a caller's spring under Reduce Motion (DR4).
  • Resolve the adaptive compact rule from spec:AST-012 FR9's definition (below the effective md width or under 500 px tall) instead of a 744 px width of its own.
  • Keep Button's press off the ::before callers style through xstyle, or ship that change as [breaking] with its migration (spec:AST-017 FR7).
  • Correct the gesture-host, press-tracking, browser-negotiation and constants-table changesets, the holdSelection export comment and draft FR11's cell delay, and name the external system in the Effect at gestures/useGestureRecognizer.ts:81-83.

[Automated review]

default: null,
[ENABLED]: {default: null, [PRESSED_FADING]: release},
},
animationDuration: {default: null, [PRESSED_FADING]: CONTROL_RELEASE},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Under Reduce Motion with a coarse pointer this release still runs 250 ms on Button's ::before (and controlOver at :222 on the cards), where main ran 200 ms. design:reduced-motion DR2 caps it at an opacity change of 200 ms or less; the checkbox already carries that arm (CheckboxInput.tsx:158).

export const TOUCH_QUERY = '(pointer: coarse)';

/** The viewport is compact: layout behaviours. */
export const COMPACT_QUERY = '(width < 744px), (height < 500px)';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spec:AST-012 FR9 (current) 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" (architecture:public-component-api INV17 agrees). With 744 px, a coarse screen 744–767 px wide loses the bottom sheet and, as before, a theme that overrides md is ignored.

? {}
: {velocity: {opacity: -timing.velocity}}),
},
{at, reducedMotion: 'keep'},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reducedMotion: 'keep' (here and at :293) plays a caller's spring or the 350 ms deselect unchanged under Reduce Motion, and the animator substitutes only travel anyway, so an opacity deselect is never shortened. holdSelection is public, so this is the opt-out design:reduced-motion DR4 rules out; under reduced motion the deselect should be an opacity change of 200 ms or less on a coarse pointer and immediate otherwise.

Comment thread packages/core/src/Button/Button.tsx Outdated
interactionOverlayStyles.backgroundImage,
// The press the controller writes, on a layer under the label whose
// release runs on the compositor (UIButton's highlight release).
pressLayerStyles.control,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This layer claims Button's root ::before (opacity: 0 at rest, z-index: -1, isolation: isolate), so a ::before a caller styles through xstyle disappears, and the changeset tells callers to change their source. Under spec:AST-017 FR1/FR7 that is an incompatibility, tagged [breaking] with its migration, not a patch [fix]; painting the press on an element Button owns avoids it.

Comment thread .changeset/gesture-host.md Outdated
'@astryxdesign/core': patch
---

[chore] Internal: the gesture system's DOM host (claims, click suppression, the browser's takeover, the pointer behind a contact). Not exported from the package entry point; no component uses it yet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every pressable now runs the gesture host: usePressFeedback calls usePressAttribute (hooks/usePressFeedback.ts:82), which builds on getGestureHost (gestures/delegatedPress.ts:135). "No component uses it yet" tells consumers to expect no runtime change.

Comment thread .changeset/gesture-press-tracking.md Outdated
'@astryxdesign/core': patch
---

[chore] Internal: UIControl press tracking with control and cell profiles, the shared press paint, delegated press tracking and the gesture hooks. Not exported from the package entry point; no component uses them yet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

installPressAttribute creates a PressTracker for every touch on a pressable (gestures/delegatedPress.ts:177), so "no component uses them yet" is not what ships.

Comment thread .changeset/uikit-constants-table.md Outdated
'@astryxdesign/core': patch
---

[chore] Internal: the UIKit constants table for touch presentations (springs, scroll physics, timing curves and navigation values), each value with its source and a checked list of the simulator measurements still owed. Not exported from the package entry point; no component reads it yet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PressTracker, which every pressable runs, reads this table (gestures/PressTracker.ts:125, :130-132), so "no component reads it yet" is not what ships.

Comment thread packages/core/src/utils/index.ts Outdated

// A list row kept selected while a navigation covers it, deselected as the
// navigation pops (UIKit's push-from-a-row). Exported for @astryxdesign/lab's
// navigation stack, which calls it with the activation that pushed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No package in the repository calls holdSelection at this head (only a Storybook story does), so "which calls it" describes a caller that does not exist yet.

}, [ref, recognizer, manageStyle]);

useIsomorphicLayoutEffect(() => {
recognizer.setEnabled(isEnabled);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turning isEnabled off mid-gesture cancels the recognizer here (GestureRecognizer.ts:548-552) and runs the consumer's onGesture inside this layout Effect. architecture:react-component-runtime INV3 asks the Effect to name the external system it synchronizes; a one-line comment naming the gesture recognizer would do.

off and on as the finger drags out and back, and activates on a lift inside.
A cell (a table or collection cell) highlights 150 ms after touch-down inside
a scroll view and drops the highlight when the scroll wins. The 150 ms delay
applies only inside scroll views. **Proposed; pending owner review:** this

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cell profile waits 150 ms wherever the row sits (gestures/PressTracker.ts:129, highlightDelay: 'always'), and .changeset/press-control-profile.md promises the same, so this sentence describes a press no list row has.

@vjeux
vjeux force-pushed the uikit/c06-indicator-press-layer branch from e0b28b4 to dc3093e Compare October 25, 2026 01:35
@vjeux

vjeux commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Pushed in dc3093e. The release now reads the shared clocks (pressConsts, cellPressConsts) and, under Reduce Motion, the policy's fade (reducedMotionFadeConsts) in place of a local cap. Its touch keys stay literal, because the StyleX build gives an imported at-rule key a different priority. #8331's test pins those literals to the resolver once it is in this base.

vjeux added 9 commits October 29, 2026 13:34
…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.
…ame window

Both 'a dip held before its first frame lands once the hold is …' proofs
waited for no running animation, then one frame and a timeout, assuming
the next step starts within a frame. The dip lands on a timer due on the
call's own clock, though, and the proofs passed only because the evaluate
round trips covered the gap: in WebKit on a loaded box the window closed
first and the proof read the element mid-dip (opacity 0, translate none;
reproduced 1 in 4 with the round trips removed). They now wait on the
call's own finished, then on the browser's own animations still running
or waiting for their first frame, and read nothing from the animator.
…ment at once and settles then

A Reduce Motion dip whose landing runs more than its whole fade back in
late (a hold released long after the dip was due) played a 1 ms opacity
animation, which WebKit keeps pending until its next frame, and settled
the call on a 1 ms timer: the call's finished could resolve while the
element still showed opacity 0. The animator's notes say the element
appears at once there. Fails now: the landing plays one opacity animation
and the call has not settled.
…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.
…element at once and settles

A Reduce Motion landing that runs at least the whole fade back in late
had nothing left of the cross-fade, but still played a 1 ms opacity
animation and settled the call 1 ms later. WebKit keeps that animation
pending until its next frame, so the call's finished could resolve with
the element still at opacity 0. Such a landing now writes the end opacity
at once (handing a borrowed one back to the stylesheet), plays nothing,
and settles the call then, as the animator's notes say.
@vjeux
vjeux marked this pull request as draft October 29, 2026 22:34
vjeux added 17 commits October 29, 2026 16:05
…caps read the Reduce Motion constant) into the meta#8118 review base
…o the press release

The overlay's reduced-motion release stays this branch's own arms, which
already read the Reduce Motion constant (reducedMotionFadeConsts); the
controller's clock cap and the row layer's take meta#8890's and meta#8077's.
…ot the timing table

From review (an inline comment on meta#8077 about this line): the press
controller, which every pressable component loads, imported the whole
UIKit timing table for one value, its reduced-motion cap, while
architecture:component-style-authoring INV6 ships a component only the
rows of a shared table it uses. It now reads the fade's own export,
reducedMotionCrossFade.durationMs, as the animator does; the value is
the same 200 ms.

Red first: a bundle of pressFeedback.ts carried the table's other rows
(stateChangeMs, buttonReleaseMs) on the change before this one; it now
carries the fade alone.
…th dividers drawn

From review on meta#8077: component:List FR11's verification asks for a
pressed row between two divided rows, and no case used a list with
dividers. A touch case opens the Interactive story with hasDividers,
holds the middle row and releases it, and checks the divider above it and
its own divider below, by computed border and by the pixels of each
divider's strip, while held, halfway through the release fade and after
it; each strip must differ from the row's surface just inside it at rest,
so a match is a drawn divider.

It passes in Chromium and WebKit. A page style that hides a pressed row's
divider fails it while the row is held.
…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.
… judges its first frame on that frame's time

'a spring started right before a block plays in full after it' waited a
fixed 300 ms of wall clock after the block, then read the screencast. In
the full interaction run on a loaded box no moving frame had reached the
screencast by then, and the proof failed on an empty read. It now waits
until the page shows the box a quarter of the way along and three more
frames reach the screencast. Its bound changes form, not strength: the
curve's own start (the handle's started) must come after the block, and
the first moving frame may be no further along than a curve started a
frame before the block ended, at that frame's own time, which is the old
30 px for a frame right after the block and still holds when a loaded
machine sends frames sparsely. A curve dated at the call is 107 px in.
…as it is now

meta#8118's fold moves a row's own press layer from interactionOverlayStyles.cell
to rowPressLayerStyles.cell (utils/rowPressLayer.ts); the comment that says the
checkbox's release runs in step with it now names it there.
…e surface

releaseAnimationsOf counted only the press owner's own animations (on it,
or its ::before or ::after). A component that draws its release on a
descendant, such as CheckboxInput's release on its box wrapper's ::after,
then lost the end of it: fading went at the 200 ms clock while the 250 ms
release still ran, and the frame it went relaid and repainted the box.

It now also counts a CSS release animation on a descendant whose nearest
press owner (closest [data-astryx-pressable]) is this element, the same
nearest-owner rule the layers read by, so a press owner nested inside
plays its own press's release and never extends this one.

Red first: a unit case whose release runs on a box inside the row found
fading gone at the clock on the change before this one; it now holds to
the release's end. A second case keeps a nested press owner's release,
on it or inside it, out of the row's.
…ts own timeline

One run measured a 250 ms release whose `fading` lasted 241.6 ms on the
page's clock: an animation's start is a frame's time, which can come
before the write, so a bound on the page's clock read a whole release as
short. The check now takes the animation's start from its own timeline
(when it is ready) and the timeline's time when `fading` goes, and asks
that it ran to its end; WebKit drops the animation at the removal, so
its progress cannot be read there.
…ase counts when this is its nearest press owner, and meta#8360 d0f5fd5) into the meta#8118 review base
…eta#8360 d0f5fd5) into the press release

meta#8890's release end now counts a descendant's release animation when
this element is its nearest press owner, so a control whose layer is a
child's (a checkbox's box wrapper) keeps `fading` until that layer's
release ends. [skip ci]
…he descendant release) into the indicator press layer [skip ci]

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's decision on four spec deltas this pull request carries: the reworded component:Button FR15 (a 250 ms finger release where the record says 200 ms), a read-only checkbox painting no press (against design:user-states DR1 and DR2), the case-exact astryx component lookup, and astryxStylex's new treeshakeCompensation default, the last two with no governing spec yet. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as one. The indicator release itself now keeps fading after a blocked main thread, but these changes are still needed whatever the decision:

  • Land #8118, #8219 and #8360 first and rebase, so this diff is the indicator change alone (it is 35,764 authored lines today).
  • If the read-only change stays, add mouse before-and-after evidence for a read-only CheckboxInput and CheckboxList row (spec:AST-017 FR59).
  • Justify or trim the 1.0–1.4 KB CSS growth the size report flags on 11 components, and let CI report this head.
  • Make the checkbox mark honour the mouse press marker as Button's layer does, or narrow that marker's 200 ms contract (utils/pressFeedback.ts:103-111).

[Automated review]

const isRowPressSurface = use(CheckboxRowPressContext);
// A checkbox that cannot change paints no press: disabled, or read-only,
// whose value is shown but refuses every change.
const paintsPress = !isDisabled && !isReadOnly;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A read-only checkbox now paints no press with a mouse either, which changes fine-pointer behavior: spec:AST-017 FR59 asks for mouse before-and-after evidence, and design:user-states DR1 asks every enabled CheckboxInput indicator to paint during a hold (DR2 exempts only disabled controls). This waits on the open owner decision.

default: null,
'@media (pointer: coarse)': 'var(--_indicator-press-release, none)',
},
animationDuration: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mark has no [data-astryx-press-input] arm, so a mouse press on a touch-first device plays the control's 250 ms ease-in-out here while Button's layer switches to the 200 ms clock the marker promises (utils/pressFeedback.ts:103-111, utils/controlPressLayer.ts:216-226). Add the same arm, or narrow the marker's contract.

This branch was successfully deployed

1 active deployment
Preview — 63b0be31 Deployed Oct 30, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review needs:spec-owner-review Current knowledge records await owner approval

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants