Skip to content

feat(List): animate row inserts, removals and moves through the animatedLayout handle - #8106

Draft
vjeux wants to merge 1980 commits into
meta:mainfrom
vjeux:uikit/c11-list-updates
Draft

vjeux wants to merge 1980 commits into
meta:mainfrom
vjeux:uikit/c11-list-updates

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Review only this PR's change: vjeux/astryx@0249148...cd9fb69 (depends on #8088).

User need

On iOS, when a table's rows change, the others slide to their new places, so you can see what changed. In Astryx, a List's rows jumped in one frame.

Problem and solution fit

What led to it: this is part of a program to make Astryx's list components behave like iOS tables on phones, and nothing animated the difference between two renders. component:List FR12 (current) puts row updates on the animatedLayout handle (#8568).

With a handle and an itemKey on each row, an inserted row enters, and a removed row collapses in place, inert and hidden from assistive technology until it's gone. A row crossing others rides above them on the list's surface. The rows after a change, and the content after the list, glide to their new places. Every part starts on one frame, on the module's shared motion, under every pointer. A row without an itemKey changes at once, and so does the whole list without a handle. Reduce Motion follows the module's policy. The handle is in List's context, so a row's own layout changes can report to it.

A row a swipe carried away exits as itself, with no element added: as a change is measured, a row's own translation moves onto its children, so its box sits at its place in the list while it shows the same thing, and a row that stays gets the translation back after the commit. On a long list, an update reads only the rows near the visible part: the list reports the rows in view and its last row, found by halving, so the module's walk stays near the view.

Size of this pull request's own change (gzip JS/CSS, the compare link's base → head): List +8,011 B JS, CheckboxList +7,850 B JS, ListItem +215 B JS and +80 B CSS; TreeList and Item unchanged. Item, TreeList, RadioList and useMenuPress change in the pull requests this one depends on, which report their own sizes.

Current specification

component:List FR12 (current); spec:AST-105 FR1–FR5; spec:AST-103 DEC-4 (itemKey is a row's identity).

Public delta and caller burden

<List animatedLayout={useAnimatedLayout({containerRef})}> with <ListItem itemKey={id}>.

Design and alternatives

Evidence

iOS 26.2 simulator, Mobile Safari, touch input; a real device is the release gate. Recorded at 1eb6bc0; at this head, removal, insert, reorder and swipe were recorded again and match.

Removal Insert Reorder
Removal Insert Reorder
Swipe away Reduce Motion
Swipe away Reduce Motion

With a mouse, from the List story in Chromium (remove, insert, move):

Before (no handle, as on main) After
Before After

From the automated reviews, each with a test that failed on the previous head where it applied: one update of a 1,000-row list reads 234 row places (4,001 before); a removal near the top makes no pairwise crossing checks (996,076 before); the swiped row exits as itself, at its place; plus the earlier fixes (an inserted row fades in under Reduce Motion, the last row's exit never paints over the content after the list, a caller's inline overflow-y stays).

Scope

  • One capability is added.
  • Unrelated fixes, cleanup, and policy changes were removed or split.
  • Consumer docs, focused tests, and migration evidence ship with the feature.
  • Public text and artifacts contain no internal Meta context.

Testing

  1. Open Storybook's List "Animated changes" story and tap "Remove the second row": it conceals in place while the rows below, the caption and the buttons glide up.
  2. Tap "Insert a row second": the new row enters while the rows below glide down, together.
  3. Tap "Move the last row first": it glides to the top above the rows it crosses.
  4. In "Animated changes swipe", swipe Sent fully left: it exits with its action showing, and the rows below glide up.
  5. With a mouse, the same motion. With Reduce Motion, a phone cross-fades, and a mouse changes at once.

What was run: steps 1–4 and Reduce Motion under a finger on the iOS 26.2 simulator (the clips above), step 5 with a mouse (the recordings above); unit tests (2215, the List, Item, TreeList, motion, utils and hooks suites); the List, TreeList and animated layout browser specs in Chromium (132, 2 skipped) and WebKit (117, 17 skipped).

@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:14am 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:36 AM UTC

@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.

This pull request needs an engineering owner's decision before it can be approved: no current spec governs List's new hasAnimatedChanges and followingContent props, and the draft AST-094 FR8 (a move fades through under Reduce Motion) conflicts with the code keeping the slide (REDUCED_MOTION = 'keep'). An engineering owner can provide or identify the governing List spec, settle FR8 against 'keep', and record the decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so if they are an engineering owner. Independently of that decision, these need changes:

  • The diff carries the whole stack (17,607 authored lines); once the predecessor pull requests land it should contain only this pull request's two commits (998 lines).
  • With followingContent="moves", content below the list that layout did not move (for example a footer held by margin-top: auto) is shifted by the list's height change and slides back (contentBelow in rowMotion.ts).
  • Row positions are recorded only on List renders, so a layout change between renders (a rotation, a row growing on its own) replays as motion at the next render.
  • With hasAnimatedChanges off, every List still subscribes to (pointer: coarse) and reads every row's layout on each render.

[Automated review]

Comment thread packages/core/src/utils/rowMotion.ts Outdated
Comment on lines +314 to +321
// Below, not beside: a flex or grid neighbour does not move when the
// component changes height.
const top =
next.getBoundingClientRect().top - presentation(next, 'y', motionNow());
if (top < bottom - 1) {
continue;
}
below.push(next);

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 keeps any in-flow element whose top is below the list, but never checks that layout moved it. When an ancestor absorbs the list's height change (a footer held at the bottom by margin-top: auto, the block after a grid row kept open by a taller column, a fixed-height or overflow: hidden parent), useListUpdateMotion still shifts it by -grew and springs it back, so content that did not move jumps and slides. Recording each candidate's position at every commit, as the rows' are, and springing it by its own measured shift would avoid that.

Comment on lines +90 to +94
const before = placesRef.current;
placesRef.current = places;
const height = element.offsetHeight;
const grew = height - heightRef.current;
heightRef.current = height;

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 baseline is only refreshed here, after a List render. A rotation that rewraps rows, a row that grows from its own state, or a late font moves rows without rendering List, and the next unrelated render replays that change as motion for the rows and the content below. Refreshing the places and height when layout changes without a commit (for example from a resize observation held only while hasAnimatedChanges is on) would stop the replay.

enabled: boolean,
followingContent: 'moves' | 'stays' = 'moves',
): void {
const animates = useMotionPresentation() === 'uikit' && enabled;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

List calls this hook for every instance, so with hasAnimatedChanges off each List still subscribes to (pointer: coarse) here and, below, reads every row's offsetParent/offsetTop and its own offsetHeight on every render before the enabled check. Returning before the measurements when the prop is off (clearing placesRef), and reading the pointer inside the effect instead of subscribing, keeps the opt-in free for lists that do not use it.

@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

Item (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 284B 284B 0B
Lines of Code 1372 1424 -
Complexity Very High Very High (140) -
TreeList (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 648B 648B 0B
Lines of Code 1068 1259 -
Complexity Very High Very High (138) -

Component Size

Component Import before Import after Delta CSS before CSS after Delta
charts/Chart N/A 64.7KB N/A N/A 20B N/A
charts/ChartAxis N/A 6.7KB N/A N/A 112B N/A
charts/ChartLegend N/A 54.0KB N/A N/A 20B N/A
charts/ChartTooltip N/A 54.0KB N/A N/A 20B N/A
core/AlertDialog 57.3KB 57.8KB +489B 12.5KB 12.6KB +21B
core/AppShell 66.7KB 67.1KB +432B 11.8KB 11.8KB +22B
core/AspectRatio 3.7KB 3.8KB +66B 3.5KB 3.5KB +40B
core/Avatar 37.8KB 37.9KB +52B 6.3KB 6.3KB +23B
core/AvatarGroup 40.2KB 40.2KB +51B 6.4KB 6.5KB +22B
core/AvatarGroupOverflow 40.3KB 40.8KB +512B 6.4KB 6.4KB +23B
core/AvatarStatusDot 7.1KB 7.1KB +61B 3.4KB 3.4KB +41B
core/Badge 3.9KB 4.0KB +64B 3.8KB 3.9KB +39B
core/Banner 48.5KB 49.0KB +492B 10.1KB 10.1KB +21B
core/BaseTypeahead 55.0KB 55.4KB +468B 10.1KB 10.1KB +21B
core/Blockquote 3.5KB 3.6KB +66B 3.4KB 3.4KB +39B
core/BottomSheet 33.9KB 34.0KB +71B 7.9KB 7.9KB +24B
core/BottomSheetSwitcher 14.8KB 14.8KB +62B 3.3KB 3.3KB +41B
core/BreadcrumbItem 67.0KB 68.2KB +1.1KB 11.2KB 11.5KB +361B
core/Breadcrumbs 22.8KB 22.9KB +64B 3.3KB 3.3KB +40B
core/Button 43.7KB 44.2KB +494B 9.3KB 9.3KB +21B
core/ButtonGroup 8.4KB 8.5KB +67B 3.3KB 3.3KB +40B
core/Calendar 55.6KB 56.1KB +469B 10.2KB 10.2KB +23B
core/Card 6.4KB 6.5KB +66B 6.3KB 6.4KB +42B
core/Carousel 50.9KB 51.3KB +423B 10.3KB 10.4KB +22B
core/Center 4.2KB 4.2KB +63B 4.6KB 4.6KB +42B
core/ChatComposer 61.3KB 61.8KB +472B 11.0KB 11.0KB +20B
core/ChatComposerDrawer 24.3KB 24.3KB +65B 4.5KB 4.6KB +39B
core/ChatComposerInput 56.2KB 56.6KB +470B 10.2KB 10.2KB +20B
core/ChatComposerTokenElement 56.2KB 56.6KB +469B 10.2KB 10.2KB +20B
core/ChatDictationButton 47.2KB 47.6KB +472B 9.6KB 9.7KB +21B
core/ChatLayout 49.5KB 50.0KB +502B 10.1KB 10.1KB +22B
core/ChatLayoutScrollButton 47.0KB 47.5KB +474B 9.7KB 9.8KB +21B
core/ChatMessage 23.4KB 23.5KB +65B 3.5KB 3.6KB +39B
core/ChatMessageBubble 7.0KB 7.1KB +67B 3.6KB 3.6KB +42B
core/ChatMessageList 29.3KB 29.4KB +69B 7.8KB 7.8KB +21B
core/ChatMessageMetadata 26.4KB 26.5KB +65B 4.0KB 4.0KB +22B
core/ChatSendButton 46.4KB 46.9KB +487B 9.3KB 9.3KB +20B
core/ChatSystemMessage 6.7KB 6.8KB +71B 3.7KB 3.7KB +39B
core/ChatTokenizedText 6.9KB 7.0KB +63B 3.9KB 3.9KB +40B
core/ChatToolCalls 32.4KB 32.5KB +69B 9.1KB 9.1KB +22B
core/CheckIndicator 9.2KB 9.3KB +64B 3.7KB 3.8KB +41B
core/CheckboxIndicator 4.1KB 4.1KB +65B 3.8KB 3.8KB +22B
core/CheckboxInput 50.5KB 50.9KB +452B 9.5KB 9.5KB +22B
core/CheckboxList 46.6KB 54.3KB +7.7KB ⚠️ 8.3KB 8.3KB +22B
core/CheckboxListItem 54.7KB 58.5KB +3.8KB ⚠️ 10.6KB 11.2KB +609B
core/Citation 26.5KB 26.6KB +68B 4.3KB 4.3KB +40B
core/ClickableCard 12.8KB 13.3KB +531B 6.9KB 7.0KB +46B
core/Code 3.5KB 3.6KB +63B 3.4KB 3.4KB +41B
core/CodeBlock 61.2KB 61.6KB +476B 10.8KB 10.9KB +21B
core/Collapsible 18.7KB 19.1KB +423B 4.4KB 4.4KB +41B
core/CollapsibleGroup 6.4KB 6.4KB +71B 3.2KB 3.2KB +40B
core/CommandPalette 51.5KB 52.0KB +474B 12.0KB 12.0KB +24B
core/CommandPaletteEmpty 6.1KB 6.2KB +64B 3.3KB 3.4KB +42B
core/CommandPaletteFooter 23.6KB 23.7KB +69B 3.5KB 3.6KB +39B
core/CommandPaletteGroup 3.5KB 3.5KB +64B 3.3KB 3.4KB +39B
core/CommandPaletteInput 32.9KB 33.0KB +60B 8.3KB 8.3KB +23B
core/CommandPaletteItem 8.4KB 8.9KB +487B 3.5KB 3.5KB +40B
core/CommandPaletteList 22.8KB 22.9KB +61B 3.3KB 3.3KB +41B
core/ComplexSelector 56.5KB 57.0KB +479B 10.8KB 10.8KB +20B
core/ContextMenu 75.3KB 87.3KB +12.0KB ⚠️ 14.8KB 15.4KB +626B
core/ContextMenuItem 53.5KB 54.0KB +448B 9.8KB 9.8KB +21B
core/DateInput 100.2KB 100.7KB +506B 14.9KB 15.0KB +20B
core/DateRangeInput 70.1KB 70.6KB +477B 11.4KB 11.4KB +21B
core/DateTimeInput 110.4KB 110.9KB +465B 15.4KB 15.4KB +20B
core/Dialog 16.2KB 16.3KB +64B 7.1KB 7.1KB +23B
core/DialogHeader 49.5KB 50.0KB +499B 10.0KB 10.0KB +20B
core/Divider 6.5KB 6.5KB +64B 3.6KB 3.6KB +43B
core/Drawer 31.6KB 31.6KB +57B 8.0KB 8.1KB +23B
core/DrawerHeader 49.6KB 50.1KB +482B 11.4KB 11.4KB +21B
core/DropdownMenu 73.4KB 74.6KB +1.2KB 15.0KB 15.6KB +612B
core/DropdownMenuCheckboxItem 16.4KB 17.5KB +1.2KB 5.7KB 6.1KB +351B
core/DropdownMenuDivider 6.6KB 6.6KB +61B 3.6KB 3.6KB +42B
core/DropdownMenuGroup 6.1KB 6.1KB +69B 3.3KB 3.3KB +39B
core/DropdownMenuItem 15.6KB 16.8KB +1.2KB 5.3KB 5.7KB +372B
core/DropdownMenuRadioGroup 6.0KB 6.1KB +62B 3.2KB 3.2KB +43B
core/DropdownMenuRadioItem 16.4KB 17.6KB +1.2KB 5.7KB 6.1KB +351B
core/DropdownMenuSubMenu 58.7KB 59.8KB +1.1KB 9.7KB 10.0KB +360B
core/EmptyState 7.7KB 7.8KB +64B 3.5KB 3.6KB +42B
core/Field 44.8KB 44.9KB +65B 8.1KB 8.2KB +22B
core/FieldLabel 40.3KB 40.4KB +61B 6.7KB 6.7KB +21B
core/FieldStatus 10.6KB 10.6KB +70B 4.4KB 4.4KB +22B
core/FileInput 54.8KB 55.2KB +442B 10.5KB 10.5KB +22B
core/FormLayout 6.3KB 6.3KB +66B 3.4KB 3.4KB +40B
core/Grid 4.3KB 4.4KB +67B 3.7KB 3.7KB +40B
core/GridSpan 3.4KB 3.5KB +63B 3.2KB 3.3KB +40B
core/HStack 7.3KB 7.3KB +62B 4.9KB 4.9KB +44B
core/Heading 9.1KB 9.2KB +64B 6.8KB 6.8KB +23B
core/Icon 9.0KB 9.1KB +67B 3.7KB 3.8KB +41B
core/IconButton 43.7KB 44.2KB +488B 9.3KB 9.3KB +21B
core/InputGroup 45.4KB 45.5KB +60B 8.2KB 8.2KB +22B
core/InputGroupText 6.3KB 6.4KB +66B 3.7KB 3.8KB +40B
core/Item 11.4KB 12.5KB +1.1KB 4.7KB 5.0KB +372B
core/Kbd 23.4KB 23.4KB +65B 3.5KB 3.5KB +40B
core/LayerProvider 64.0KB 64.5KB +475B 10.3KB 10.3KB +24B
core/Layout 8.1KB 8.2KB +63B 5.5KB 5.5KB +43B
core/LayoutContent 7.4KB 7.5KB +64B 4.9KB 5.0KB +43B
core/LayoutFooter 7.3KB 7.4KB +68B 4.8KB 4.9KB +42B
core/LayoutHeader 7.4KB 7.4KB +65B 4.8KB 4.9KB +42B
core/LayoutPanel 7.6KB 7.6KB +65B 4.8KB 4.9KB +43B
core/Lightbox 51.9KB 52.3KB +448B 10.0KB 10.0KB +22B
core/Link 44.1KB 44.5KB +470B 7.7KB 7.8KB +25B
core/List 6.7KB 14.6KB +7.8KB ⚠️ 3.4KB 3.5KB +40B
core/ListItem 54.1KB 57.9KB +3.8KB ⚠️ 10.6KB 11.2KB +609B
core/Markdown 145.0KB 157.0KB +12.0KB ⚠️ 17.6KB 18.2KB +613B
core/MetadataList 23.5KB 23.6KB +71B 3.6KB 3.6KB +42B
core/MetadataListItem 6.5KB 6.5KB +64B 3.4KB 3.4KB +43B
core/MobileNav 51.7KB 52.1KB +410B 10.2KB 10.3KB +23B
core/MobileNavToggle 46.9KB 47.3KB +472B 9.6KB 9.6KB +21B
core/MoreMenu 73.7KB 74.9KB +1.2KB 15.0KB 15.6KB +612B
core/MultiSelector 73.6KB 74.1KB +483B 14.8KB 14.8KB +23B
core/NavHeadingMenu 8.9KB 9.0KB +65B 3.2KB 3.3KB +42B
core/NavIcon 3.4KB 3.5KB +63B 3.3KB 3.3KB +42B
core/NumberInput 56.4KB 56.9KB +493B 10.9KB 10.9KB +20B
core/Outline 29.0KB 29.5KB +471B 4.0KB 4.1KB +40B
core/OverflowList 8.5KB 8.6KB +67B 3.5KB 3.5KB +40B
core/Overlay 19.5KB 19.6KB +43B 3.8KB 3.9KB +17B
core/Pagination 85.5KB 86.6KB +1.2KB 15.5KB 15.8KB +347B
core/Popover 50.3KB 50.7KB +480B 12.2KB 12.2KB +20B
core/PowerSearch 166.6KB 178.3KB +11.8KB ⚠️ 20.1KB 20.4KB +354B
core/ProgressBar 7.7KB 7.8KB +66B 6.6KB 6.6KB +20B
core/RadioIndicator 3.8KB 3.9KB +61B 3.7KB 3.7KB +20B
core/RadioList 46.2KB 46.3KB +64B 8.2KB 8.2KB +21B
core/RadioListItem 52.8KB 53.9KB +1.1KB 9.8KB 10.2KB +351B
core/ResizeHandle 25.5KB 25.5KB +71B 3.9KB 3.9KB +41B
core/Section 7.5KB 7.6KB +67B 6.2KB 6.2KB +42B
core/SegmentedControl 39.1KB 39.1KB +59B 6.3KB 6.4KB +22B
core/SegmentedControlItem 8.6KB 9.1KB +495B 3.9KB 3.9KB +41B
core/SelectableCard 12.8KB 13.3KB +523B 7.1KB 7.2KB +46B
core/Selector 77.0KB 78.2KB +1.1KB 15.2KB 15.5KB +349B
core/SelectorOption 14.7KB 15.9KB +1.2KB 5.2KB 5.6KB +372B
core/SideNav 64.6KB 65.0KB +419B 10.7KB 10.7KB +21B
core/SideNavCollapseButton 47.3KB 47.8KB +482B 9.6KB 9.6KB +20B
core/SideNavHeading 55.4KB 55.8KB +442B 10.3KB 10.3KB +20B
core/SideNavItem 55.1KB 55.6KB +461B 10.3KB 10.3KB +21B
core/SideNavSection 6.5KB 6.6KB +59B 3.6KB 3.6KB +38B
core/Skeleton 3.8KB 3.8KB +60B 3.6KB 3.6KB +22B
core/Slider 49.0KB 49.1KB +79B 8.7KB 8.7KB +20B
core/Spinner 26.7KB 26.8KB +55B 7.5KB 7.5KB +19B
core/Stack 7.1KB 7.1KB +62B 4.9KB 4.9KB +44B
core/StackItem 6.0KB 6.1KB +63B 3.3KB 3.3KB +38B
core/StatusDot 19.0KB 19.0KB +59B 6.2KB 6.2KB +19B
core/Step 33.0KB 33.5KB +484B 5.5KB 5.5KB +23B
core/Stepper 48.1KB 48.5KB +463B 9.7KB 9.8KB +21B
core/Switch 49.0KB 49.4KB +453B 9.5KB 9.5KB +21B
core/Tab 9.7KB 10.2KB +501B 4.1KB 4.1KB +41B
core/TabList 40.6KB 40.7KB +72B 7.3KB 7.3KB +22B
core/TabMenu 52.3KB 52.7KB +460B 10.2KB 10.2KB +21B
core/Table 84.5KB 96.5KB +12.0KB ⚠️ 15.3KB 15.9KB +607B
core/TableBody 3.3KB 3.3KB +63B 3.2KB 3.2KB +42B
core/TableCell 76.3KB 88.3KB +12.0KB ⚠️ 15.1KB 15.7KB +609B
core/TableFooter 3.3KB 3.3KB +63B 3.2KB 3.2KB +42B
core/TableHeader 3.3KB 3.3KB +63B 3.2KB 3.2KB +42B
core/TableHeaderCell 76.1KB 88.1KB +12.0KB ⚠️ 15.0KB 15.6KB +609B
core/TableRow 6.2KB 6.3KB +66B 3.6KB 3.6KB +40B
core/TableSelectionToolbar 52.8KB 53.3KB +473B 11.3KB 11.3KB +21B
core/Text 8.6KB 8.6KB +66B 6.8KB 6.8KB +23B
core/TextArea 50.0KB 50.1KB +80B 9.5KB 9.6KB +22B
core/TextInput 54.2KB 54.6KB +427B 10.6KB 10.6KB +20B
core/Thumbnail 51.4KB 51.8KB +476B 11.1KB 11.2KB +24B
core/TimeInput 87.5KB 87.9KB +401B 13.9KB 13.9KB +20B
core/Timer 9.2KB 9.3KB +70B 6.8KB 6.8KB +23B
core/Timestamp 27.9KB 28.0KB +71B 9.7KB 9.7KB +21B
core/Toast 60.6KB 61.0KB +488B 9.9KB 9.9KB +22B
core/ToggleButton 44.5KB 45.0KB +463B 9.4KB 9.4KB +21B
core/ToggleButtonGroup 6.3KB 6.4KB +68B 3.2KB 3.3KB +42B
core/Token 30.2KB 30.6KB +482B 4.6KB 4.7KB +41B
core/Tokenizer 69.3KB 69.8KB +470B 16.7KB 16.7KB +20B
core/Toolbar 40.1KB 40.2KB +67B 7.7KB 7.8KB +46B
core/TopNav 52.5KB 52.9KB +431B 10.5KB 10.5KB +21B
core/TopNavHeading 54.8KB 55.2KB +460B 10.1KB 10.1KB +20B
core/TopNavItem 9.0KB 9.5KB +509B 3.8KB 3.9KB +39B
core/TopNavMegaMenu 54.9KB 55.3KB +476B 11.0KB 11.0KB +21B
core/TopNavMegaMenuFeaturedCard 6.7KB 6.8KB +66B 3.4KB 3.4KB +38B
core/TopNavMegaMenuItem 9.1KB 9.6KB +505B 4.1KB 4.1KB +38B
core/TopNavMenu 54.3KB 54.7KB +450B 10.3KB 10.3KB +22B
core/TreeList 40.3KB 51.3KB +11.0KB ⚠️ 4.8KB 5.2KB +383B
core/Typeahead 62.2KB 62.6KB +476B 11.4KB 11.5KB +21B
core/TypeaheadItem 6.2KB 6.3KB +60B 3.4KB 3.5KB +39B
core/VStack 7.3KB 7.3KB +61B 4.9KB 4.9KB +44B
lab/ChartDotGL N/A 7.4KB N/A N/A 20B N/A
lab/ChartDotGLInteractive N/A 8.3KB N/A N/A 20B N/A
lab/ChartHeatmapGL N/A 8.5KB N/A N/A 20B N/A
lab/ChartStreamGL N/A 7.4KB N/A N/A 20B N/A
lab/ChatEmojiPicker N/A 66.2KB N/A N/A 773B N/A
lab/ChatReactionBar N/A 66.8KB N/A N/A 1.1KB N/A
lab/ChatReasoning N/A 7.0KB N/A N/A 967B N/A
lab/ChatTypingIndicator N/A 24.0KB N/A N/A 612B N/A
lab/ChatUnreadDivider N/A 3.6KB N/A N/A 348B N/A
lab/CircularProgress N/A 7.2KB N/A N/A 1.0KB N/A
lab/CodeEditor N/A 14.6KB N/A N/A 665B N/A
lab/CommentComposer N/A 95.2KB N/A N/A 1.9KB N/A
lab/CommentPin N/A 48.4KB N/A N/A 1.2KB N/A
lab/CommentThread N/A 228.0KB N/A N/A 2.4KB N/A
lab/Comments N/A 232.7KB N/A N/A 2.5KB N/A
lab/DiffViewer N/A 46.6KB N/A N/A 2.1KB N/A
lab/EmojiPicker N/A 64.1KB N/A N/A 722B N/A
lab/InfoTip N/A 22.0KB N/A N/A 458B N/A
lab/ListInput N/A 76.3KB N/A N/A 1.6KB N/A
lab/LogStream N/A 8.0KB N/A N/A 1.6KB N/A
lab/MobilePowerSearch N/A 159.2KB N/A N/A 1.4KB N/A
lab/MobileTokenizer N/A 93.2KB N/A N/A 809B N/A
lab/SVGIcon N/A 7.6KB N/A N/A 764B N/A
lab/Schedule N/A 60.9KB N/A N/A 3.9KB N/A
lab/Stat N/A 4.1KB N/A N/A 646B N/A
lab/ThreeDScatterGL N/A 7.6KB N/A N/A 20B N/A
lab/TourStep N/A 56.1KB N/A N/A 590B N/A
lab/TransferList N/A 76.7KB N/A N/A 1.7KB N/A
lab/TransferListSelector N/A 82.3KB N/A N/A 1.9KB N/A
richtext/RichTextEditor N/A 271.9KB N/A N/A 2.6KB N/A
richtext/RichTextEditorToolbar N/A 214.1KB N/A N/A 454B N/A
richtext/RichTextView N/A 252.3KB 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/CheckboxList +7.7KB ⚠️ +22B
core/CheckboxListItem +3.8KB ⚠️ +609B
core/ContextMenu +12.0KB ⚠️ +626B
core/List +7.8KB ⚠️ +40B
core/ListItem +3.8KB ⚠️ +609B
core/Markdown +12.0KB ⚠️ +613B
core/PowerSearch +11.8KB ⚠️ +354B
core/Table +12.0KB ⚠️ +607B
core/TableCell +12.0KB ⚠️ +609B
core/TableHeaderCell +12.0KB ⚠️ +609B
core/TreeList +11.0KB ⚠️ +383B

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 35.1KB 35.8KB +647B

Accessibility Audit

Status: 2 accessibility violation(s) found — 2 serious.

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/14 stories · Learn more
    • WCAG: 1.4.3 (Level AA)

Visual Regression

57 of 504 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 37 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.

This pull request needs an engineering owner's decision before it can be approved: no current spec governs List's new hasAnimatedChanges and followingContent props, and the draft AST-094 FR8 (a move fades through under Reduce Motion) conflicts with the code keeping the slide (REDUCED_MOTION = 'keep'). An engineering owner can provide or identify the governing List spec, settle FR8 against 'keep', and record the decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so if they are an engineering owner. Independently of that decision, these need changes:

  • The diff carries the whole stack (18,305 authored lines); once the predecessors land, or after retargeting and rebasing onto main, it should contain only this pull request's two commits (1,191 lines).
  • With followingContent="moves", content below the list that layout did not move (for example a footer held by margin-top: auto) is shifted by the list's height change and slides back.
  • The same transient translate re-anchors position: fixed elements inside that content, so a fixed action bar jumps up the screen for about half a second.
  • Row positions are recorded only on List renders, so a layout change between renders replays as motion at the next render.
  • With hasAnimatedChanges off, every List still subscribes to (pointer: coarse) and reads every row's layout on each render.

[Automated review]

Comment thread packages/core/src/utils/rowMotion.ts Outdated
Comment on lines +292 to +299
if (
style.position === 'absolute' ||
style.position === 'fixed' ||
style.position === 'sticky' ||
style.display === 'none'
) {
continue;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

contentBelow checks only the candidate's own position, but the translate it then writes makes the candidate the containing block of any position: fixed element inside it. In a fixture with an action bar fixed 16px above the bottom of a 720px viewport, inside a section after the list, removing a row moved the bar from 660px to 344px from the top, and it stayed re-anchored until releaseMotion cleared the translate about 500 ms later. Skipping candidates that contain fixed-position descendants, or defaulting followingContent to 'stays', would avoid it.

@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.

This pull request needs an engineering owner's decision before it can be approved: no current spec governs List's new hasAnimatedChanges and followingContent props, and the draft AST-094 FR8 (a move fades through under Reduce Motion) conflicts with the code keeping the slide (REDUCED_MOTION = 'keep'). An engineering owner can provide or identify the governing List spec, settle FR8 against 'keep', and record the decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so if they are an engineering owner. Independently of that decision, these need changes:

  • The diff carries the whole stack (18,321 authored lines); once the predecessors land, or after retargeting onto the stack's previous branch, it should contain only this pull request's two commits (1,191 lines).
  • With followingContent="moves", content below the list that layout did not move (for example a footer held by margin-top: auto) is shifted by the list's height change and slides back.
  • The same transient translate re-anchors position: fixed elements inside that content, so a fixed action bar jumps up the screen until the motion ends.
  • Row positions are recorded only on List renders, so a layout change between renders replays as motion at the next render.
  • With hasAnimatedChanges off, every List still subscribes to (pointer: coarse) and reads every row's layout on each render.

[Automated review]

vjeux added 2 commits October 29, 2026 15:11
…and turn it around early in the curve

The mid-motion case held every sampled frame's step to a fixed share of
the travel, so a frame the browser delayed under load carried two frames'
travel into one sample and failed (WebKit, a 55 px step on a 126 px
group). Each sample now carries its frame's time, and a step is held to
twice the outline spring's peak speed times the time since the last
frame, plus a pixel (a degree for the chevron).

The page now turns the expand around itself, on the first frame the block
moves, so the turn around lands far from either end of the curve under any
load; a tap after a poll often landed it near the end, where a restart
moved too little to tell.

A mutation that restarts the collapse from rest fails it (a 112 px step in
one 17 ms frame, against a 40 px bound); the product passes, three runs in
each engine.

@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.

This needs an engineering owner's decision before it can be approved: this head carries #8088's rewording of component:TreeList FR8, still proposed on main, so that under a finger the rows after a collapsing group hold until its rows have slid under the parent. An engineering owner records that decision by commenting /approve-spec 2aa3b3b332491c3a07850657b2bfd6e9d064d5e6 on this pull request (the author may do so as an engineering owner), or FR8 is settled through #8088 first. The List change itself follows component:List FR12; after that decision, two changes remain:

  • Land the eight predecessor pull requests first, so this diff is the 2,067-line List change rather than 35,919 authored lines.
  • Report the base-against-head size of every component the diff changes, or let the existing size line cover it once the predecessors land.

[Automated review]

| FR5 | `TreeListItemData` MUST admit `className`, `style`, and `xstyle` as one per-item styling seam. Every supplied value MUST compose on the same Item row box; an implementation MUST NOT treat current support for only one mechanism as authority to exclude the other two. | Owner decision; `architecture:public-component-api` INV5–INV6; `spec:AST-002/DEC-1` | Settled intent; implementation may be staged |
| FR6 | The Item row box is the logical element that paints the row, owns its hover state, and contains Start content, Item label and description, and End content. The styling seam MUST NOT move to the semantic `<li>` or another element without a compatibility decision. | Owner decision and the caller-owned per-row interaction boundary | Settled intent |
| FR7 | A click on an Item row outside its Item label performs the row's action: an Item with `onClick` runs it; otherwise an Item with children toggles; otherwise an Item whose only action is its link follows that link as Enter does, with modified and middle clicks following the shared enlarged-surface rule in `family:navigation-destinations`. The Chevron and controls in Start content and End content keep their own activation, and a disabled Item does nothing. | Owner decision; `family:navigation-destinations` | Settled intent; implementation may be staged |
| FR8 | Expanding or collapsing an Item with children MUST play its group's own motion: under a coarse primary pointer its rows slide out from under the parent as one block, clipped to the group's box, and back on collapse, a collapsing group's rows leaving the accessibility tree as the slide starts, while the Chevron turns, by transform only (`spec:AST-094` FR4–FR5); under a fine pointer the group appears and leaves at once. With an `animatedLayout` handle, the group and the rows after it follow `spec:AST-105`'s shared motion under every pointer (FR4, FR5); without one, the group's own slide stays touch-only and the rows after the group take their places at once (`spec:AST-105` FR2): as an expanding group appears and, under a coarse primary pointer, as a collapsing group's rows finish sliding under the parent, until which they hold their places. | UIKit outline list (`UICollectionView`), iOS 26.2, whose rows after a closing group hold until its rows have slid under the parent (0.24–0.37 s measured); `spec:AST-105` FR2 | Proposed; awaiting an engineering owner's approval |

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 rewords FR8, still "Proposed; awaiting an engineering owner's approval" on main: without a handle, the rows after a collapsing group now hold until its rows have slid under the parent, and the verification row (L279) fails rows that move earlier. An engineering owner records the decision by commenting /approve-spec with this head's full SHA on the pull request (the author may, as an engineering owner), or FR8 is settled through #8088 first.

vjeux added 10 commits October 29, 2026 16:34
…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.
…finger

From review on meta#8085: component:List FR10's verification asks for every
density, and the browser cases opened only balanced and compact lists.
A touch case opens the Interactive story at density="spacious" (the
density of the bottom-sheet menu rows): its one-line rows are 48 px,
already past the 44 px target, so their layers reach 0 px on both sides
and no two targets overlap. The story's density comes through a new args
option of the spec's openStory helper.

It passes in Chromium and WebKit. With the target raised to 56 px, it
fails: the rows then grow.
… 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.
…tor's notes drop an undefined law

The animator spec's header cited 'law M1' for compositor-only motion and
useAnimator.ts cited 'law M4' for an animator that never re-renders, and
no document defines either. The spec now cites spec:AST-094 FR5, which
keeps touch motion to transform, translate, scale, rotate and opacity;
useAnimator's sentence says what it means without the citation.
…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.

@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.

This needs an engineering owner's decision before it can be approved: this head carries #8088's rewording of component:TreeList FR8, still proposed on main, so that under a finger the rows after a collapsing group hold until its rows have slid under the parent. An engineering owner records that decision by commenting /approve-spec 8134a0f642fbb6979aefb81bf356a21d065714cb on this pull request (the author may do so as an engineering owner), or FR8 is settled through #8088 first. The List change itself follows component:List FR12; after that decision, two changes remain:

  • Land the eight predecessor pull requests first, at their current heads (#8360 has moved past the commit this branch carries), so this diff is the 2,067-line List change rather than 36,093 authored lines.
  • Report the base-against-head size of every component the diff changes, or let the existing size line cover it once the predecessors land.

[Automated review]

| FR5 | `TreeListItemData` MUST admit `className`, `style`, and `xstyle` as one per-item styling seam. Every supplied value MUST compose on the same Item row box; an implementation MUST NOT treat current support for only one mechanism as authority to exclude the other two. | Owner decision; `architecture:public-component-api` INV5–INV6; `spec:AST-002/DEC-1` | Settled intent; implementation may be staged |
| FR6 | The Item row box is the logical element that paints the row, owns its hover state, and contains Start content, Item label and description, and End content. The styling seam MUST NOT move to the semantic `<li>` or another element without a compatibility decision. | Owner decision and the caller-owned per-row interaction boundary | Settled intent |
| FR7 | A click on an Item row outside its Item label performs the row's action: an Item with `onClick` runs it; otherwise an Item with children toggles; otherwise an Item whose only action is its link follows that link as Enter does, with modified and middle clicks following the shared enlarged-surface rule in `family:navigation-destinations`. The Chevron and controls in Start content and End content keep their own activation, and a disabled Item does nothing. | Owner decision; `family:navigation-destinations` | Settled intent; implementation may be staged |
| FR8 | Expanding or collapsing an Item with children MUST play its group's own motion: under a coarse primary pointer its rows slide out from under the parent as one block, clipped to the group's box, and back on collapse, a collapsing group's rows leaving the accessibility tree as the slide starts, while the Chevron turns, by transform only (`spec:AST-094` FR4–FR5); under a fine pointer the group appears and leaves at once. With an `animatedLayout` handle, the group and the rows after it follow `spec:AST-105`'s shared motion under every pointer (FR4, FR5); without one, the group's own slide stays touch-only and the rows after the group take their places at once (`spec:AST-105` FR2): as an expanding group appears and, under a coarse primary pointer, as a collapsing group's rows finish sliding under the parent, until which they hold their places. | UIKit outline list (`UICollectionView`), iOS 26.2, whose rows after a closing group hold until its rows have slid under the parent (0.24–0.37 s measured); `spec:AST-105` FR2 | Proposed; awaiting an engineering owner's approval |

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 rewords FR8, still "Proposed; awaiting an engineering owner's approval" on main: without a handle, the rows after a collapsing group now hold under a coarse primary pointer until its rows have slid under the parent, and the verification row (L279) fails rows that move earlier. An engineering owner approves or rejects this wording by commenting /approve-spec with this head's full SHA on the pull request, or it is settled through #8088 first.

vjeux added 8 commits October 29, 2026 18:23
…ide lands when its retire runs first

Reported as a WebKit hang of the held-dip proof under an 8-worker run:
the proof's wait for the call's finished never settled. Instrumented,
the page kept drawing frames (about 60 a second) while the element sat at
opacity 0 where it started, with no animation left: the held fade out had
been finished from outside, then retired (cancelled) by its timer before
the frame that reads the call's start, so the call never started and its
landing was never armed. This case orders the retire timer before that
frame and fails on this head: the move never lands.
… that cannot come [skip ci]

The retire timer cancels a finished animation once the main thread runs
past its curve. When a sampler finished a call's animations before the
browser started them, a loaded page could run that timer before the
frame that reads the call's start: the cancel left only idle animations,
the start was never read, and the call's Reduce Motion landing was never
armed, so the element stayed hidden where it started and the call never
settled. The retire now reads such a call's start from the finished
animation before cancelling it, and with nothing left to wait on the
call keeps its own clock, so its landing still runs.
… curve, so only rows ship the row layer's constants

The shared press overlay (utils/interactionOverlay.stylex.ts) read its
release curve from the row layer's constants (cellPressConsts in
rowPressLayer.stylex.ts). The overlay reaches nearly every component,
and .stylex modules are side effects in core's package.json, so every
component then shipped the row layer's constants (the repository's size
report: +17-47 B gzip on each component without a press row), against
architecture:component-style-authoring INV6. The overlay goes back to
its own copy of the curve, main's literal; it and the row layer's copy
are both the constants table's timing.press.highlightOutCurve, and a
unit case fails if the overlay's drifts.

Red first: a source bundle of Card carried the row layer's constants
(cellRelease) on the change before this one; it now carries the overlay
and none of them.

@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.

This needs an engineering owner's decision before it can be approved: this head carries #8088's rewording of component:TreeList FR8, still proposed on main, so that under a finger the rows after a collapsing group hold until its rows have slid under the parent. An engineering owner records that decision by commenting /approve-spec 0535acf7004d0cbc538e836f2344030f1557d6bc on this pull request (the author may do so as an engineering owner), or FR8 is settled through #8088 first. The List change itself follows component:List FR12, and the latest push's animator and press-release fixes come with tests that fail before them; after that decision, two changes remain:

  • Land the eight predecessor pull requests first, so this diff is the 2,067-line List change rather than 36,239 authored lines.
  • Report the base-against-head size of every component the diff changes, or let the existing size line cover it once the predecessors land.

[Automated review]

| FR5 | `TreeListItemData` MUST admit `className`, `style`, and `xstyle` as one per-item styling seam. Every supplied value MUST compose on the same Item row box; an implementation MUST NOT treat current support for only one mechanism as authority to exclude the other two. | Owner decision; `architecture:public-component-api` INV5–INV6; `spec:AST-002/DEC-1` | Settled intent; implementation may be staged |
| FR6 | The Item row box is the logical element that paints the row, owns its hover state, and contains Start content, Item label and description, and End content. The styling seam MUST NOT move to the semantic `<li>` or another element without a compatibility decision. | Owner decision and the caller-owned per-row interaction boundary | Settled intent |
| FR7 | A click on an Item row outside its Item label performs the row's action: an Item with `onClick` runs it; otherwise an Item with children toggles; otherwise an Item whose only action is its link follows that link as Enter does, with modified and middle clicks following the shared enlarged-surface rule in `family:navigation-destinations`. The Chevron and controls in Start content and End content keep their own activation, and a disabled Item does nothing. | Owner decision; `family:navigation-destinations` | Settled intent; implementation may be staged |
| FR8 | Expanding or collapsing an Item with children MUST play its group's own motion: under a coarse primary pointer its rows slide out from under the parent as one block, clipped to the group's box, and back on collapse, a collapsing group's rows leaving the accessibility tree as the slide starts, while the Chevron turns, by transform only (`spec:AST-094` FR4–FR5); under a fine pointer the group appears and leaves at once. With an `animatedLayout` handle, the group and the rows after it follow `spec:AST-105`'s shared motion under every pointer (FR4, FR5); without one, the group's own slide stays touch-only and the rows after the group take their places at once (`spec:AST-105` FR2): as an expanding group appears and, under a coarse primary pointer, as a collapsing group's rows finish sliding under the parent, until which they hold their places. | UIKit outline list (`UICollectionView`), iOS 26.2, whose rows after a closing group hold until its rows have slid under the parent (0.24–0.37 s measured); `spec:AST-105` FR2 | Proposed; awaiting an engineering owner's approval |

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 rewords FR8, still "Proposed; awaiting an engineering owner's approval" on main: without a handle, the rows after a collapsing group now hold under a coarse primary pointer until its rows have slid under the parent, and the verification row (L279) fails rows that move earlier. It also ties the hold to the slide, while under Reduce Motion those rows fade instead and the code holds the rows after the group until the fade ends. An engineering owner can approve this wording with /approve-spec and this head's full SHA, or FR8 can be settled through #8088 first.

vjeux added 6 commits October 29, 2026 20:02
The changeset, the release lookup's comment and its unit case named a
checkbox's box as a part that draws its release inside the surface. No
component in this change does: the checkbox's release runs on the row
around it. The lookup still counts a release on a descendant whose
nearest press owner is the surface; the text now says that without the
example.

@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.

This needs an engineering owner's decision before it can be approved: this head carries #8088's rewording of component:TreeList FR8, still proposed on main, so that under a finger the rows after a collapsing group hold until its rows have slid under the parent. An engineering owner records that decision by commenting /approve-spec 0c59447f97aade3afceb1291c6ed40f0b2a0cc9a on this pull request (the author may do so as an engineering owner), or FR8 is settled through #8088 first. The List change itself follows component:List FR12, and the latest push's animator and press-overlay fixes come with tests that fail before them; after that decision, two changes remain:

  • Land the eight predecessor pull requests first, so this diff is the 2,067-line List change rather than 36,338 authored lines.
  • Report the base-against-head size of every component the diff changes, or let the existing size line cover it once the predecessors land.

[Automated review]

This branch was successfully deployed

1 active deployment
Preview — 0c59447f 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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants