Skip to content

fix(core): a control's press release runs on the compositor - #8118

Draft
vjeux wants to merge 1834 commits into
meta:mainfrom
vjeux:uikit/c01-press-release
Draft

vjeux wants to merge 1834 commits into
meta:mainfrom
vjeux:uikit/c01-press-release

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #8117 (on #8077, which carries #8890 and #8360) and #8350. Review only this PR's change: vjeux/astryx@uikit/c01-pr4-review-base...uikit/c01-press-release (base: #8117 with #8077 and #8360); of its 42 files, #8350's six (packages/cli/**, cli-component-name-case.md) are theirs, and press-paint-reduced-motion.md is the reduced-motion fade #8338 proposed, closed in favor of this PR.

#8233 lands with or right after this PR: its indicator layer takes controlPressConsts.controlReleaseEase (internal, utils/controlPressLayer.stylex.ts).

User impact

Anyone tapping a button, card or row on a touch-first device: while script runs after a tap, the release stays pressed, then snaps, or is cut off partway.

Problem and solution fit

  1. Why it occurs: the strength is a custom property feeding a gradient, repainted on the main thread.
  2. Why it harms: a tap starts work; the thread is busiest.
  3. Why it matters: the press confirms the tap; frozen, it reads as lag.

Under a coarse primary pointer, Button paints the press on a ::before (controlPressLayerStyles.control, internal) and the cards fade their ::after (controlOver): an opacity animation on UIButton's release (250 ms ease-in-out), on the compositor; under reduced motion, the Reduce Motion cross-fade. Button's layer and isolation exist only during a press. Any other pointer is unchanged: no layer, no isolation, the 200 ms release; a mouse or Space press on a touch-first device keeps it too.

Expected behavior and authority

component:Button FR15 (this PR's touch release, on #8646's text); usePressFeedback's docs; design:user-states DR1, DR5; design:reduced-motion; UIButton's release (motion/uikit/button.ts); the desktop rule, spec:AST-017 FR59/DEC-25.

Smallest restoration

  • control (Button) and controlOver (cards) in utils/interactionOverlay.stylex.ts.
  • Under a coarse pointer a finger's release on a control runs UIButton's 250 ms on its layer; a mouse's or the Space key's press there keeps 200 ms (data-astryx-press-input); a list row keeps its own layer's 200 ms.
  • A release ends when its animation does, a descendant layer's included when this surface is its nearest press owner (fix(core): a touch press's release lasts until its fade has played #8890's endAfterRelease), not on a clock from the lift that a busy first frame outruns.
  • Pressed pixels, hover and :active unchanged.
  • Size (component-size.js, gzip, base → head): Button JS 47.1 KB (+1.0 KB), CSS +738 B; ClickableCard and SelectableCard 16.1 KB (+843, +846 B), CSS +725 B; Thumbnail 54.7 KB (+990 B), CSS +733 B.

Evidence

From the iOS simulator and the Android emulator (Android's hold injector-paced):

Script blocked from the lift Before After
iOS 26.2, 1.5 s, 2× slower
Android 15 Chromium 158, 600 ms

Seeked, the release matches UIButton's curve within 0.005; no Layout or Paint.

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

  • pnpm typecheck: 0 errors; lint: 0 errors; pnpm check:knowledge; pnpm test:ui (16,810 of 16,811; the one is MultiSelector's lazy bottom-sheet case, a load red test(Selector, MultiSelector): wait for the on-demand sheet module before the first open #8642 fixes) and the node project, whose only failures fail the same way on main locally (story-tree.test.ts, release/cache.test.mjs's lock cases). The local gates are the evidence: pushes carry [skip ci], so no CI run is expected.
  • pnpm -F @astryxdesign/core build, then pnpm vitest run scripts/build-css.test.mjs: 19 pass, including that UIButton's clock, curve and isolation sit behind the coarse gate, the control layers' release on ::before and ::after, the on arm's specificity, and the reduced-motion arms (DR2: each release capped by the Reduce Motion constant on a coarse pointer; DR3 animation-name: none outranking the release otherwise).
  • pnpm vitest run packages/core/src/utils/pressPaintReducedMotion.test.ts: the reduced-motion fade outranks a control's clock.
  • pnpm storybook:build, then the Button, ListRowPress, ListRowPressPaths and ClickableCard interaction specs: Chromium 41 pass, 1 skip; WebKit (node internal/interaction-harness/scripts/webkit-container.mjs --config playwright.interaction.config.ts --project=webkit) 28 pass, 14 skip: the screencast, the rendering trace, native scrolling and the fine-pointer reduced-motion case are Chromium-only.
  • ButtonPressTiming.interaction.browser.spec.ts: a finger's release on a button runs 250 ms, a mouse's and the Space key's 200 ms, and a list row's 200 ms, each read as the release animation's own duration, with fading lasting the whole animation and going within three frames of its end. It passes in Chromium and WebKit on this PR's previous head and on this one, so moving onto the new base changes no timing.
  • The release-end rule and its tests are fix(core): a touch press's release lasts until its fade has played #8890's, which this PR takes in place of its own copy; ButtonPressRelease "after a busy first frame, the release keeps a button's layer until its strength reaches 0" passes at the head in Chromium and WebKit.
  • pnpm exec playwright test on the PressModality, PressedState, Button and CardPress a11y specs (Chromium): 37 pass.
  • The desktop rule: with a fine primary pointer, a mouse press adds no ::before and no isolation, and a finger on a touch screen beside the mouse gets the strength's 200 ms release on the element, as before (ButtonPressRelease, 3 Chromium cases with (pointer: coarse) asserted false; Button.test.tsx, the three cards' tests and pressFeedback.test.ts in unit). All fail on the previous head (3570663ae8) and pass here.
  • ButtonPressRelease "the release of a button does no Layout or Paint per frame" failed (Paint 2): the browser dropped the 125 ms press-scale transition's layer mid-release. With will-change: transform while the press attribute is on or fading, a trace shows Paint only at the touch and the release's start and end; 5 of 5 pass.
  • At rest under a coarse pointer, Button has no ::before and no isolation, so a ::before its caller styles keeps its opacity, z-index and position: Button.test.tsx (layerOnlyWhilePressed) and ButtonPressRelease ("no press layer and no isolation at rest") fail on the previous head (fa86418) and pass here.
  • motion/uikit/values.test.ts (from feat(core): the internal touch foundation: press tracking and the animator #8076) pins the press release curve to press.highlightOutCurve; it reads the fading arm outside the coarse-pointer and Reduce Motion media, since a control's coarse release plays UIButton's curve. It fails on the merged head without that and when the deselect's curve drifts.
  • pressFeedback.test.ts "another painter's fade never cuts the controller's own release short": a fade another painter left running ends at the controller's write; it fails when writePress leaves that fade running.
  • With fix(cli): component lookup opens only a file that exports that exact name #8350 in the base, motion/uikit/button.ts no longer answers astryx component button: pnpm vitest run --project node packages/cli/api/component packages/cli/foundation/discovery pass.
  • controlPressConsts.controlReleaseEase sits beside controlReleaseMs in the internal utils/controlPressLayer.stylex.ts, off core's published paths (UIButton's curve, for fix(core): a checkbox or radio press releases on the compositor #8233's indicator layer); pressFeedback.test.ts holds it to button.highlight.releaseCurve.

Writing rubric results

component:Button (FR15: the touch press's ::before and release, added to #8646's text):

  • R1 narration: none; FR15's new sentence states what Button does, in the present tense.
  • R2 research: none in the record.
  • R3 evidence: none names a pull request or commit.
  • R4 rejected alternatives: none.
  • R5 change references: none.
  • R6 open questions: OQ1 is unanswered and lists no options.

Stack (merge in this order): #8360 → #8890 → #8077 → #8117 → this PR; #8350 at any point before it.

@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 2:48am UTC

Request Review

@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:12 AM UTC

@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Oct 24, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 24, 2026
@vjeux
vjeux force-pushed the uikit/c01-press-release branch from ed0fcb5 to 3f6d699 Compare October 24, 2026 06:34

@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 is on hold: the press changes here rest on the new draft spec:AST-094, namely UIKit as the touch reference (DEC-1), the compositor release (FR5) and the control press profile (FR11). That includes UIButton's 250 ms release and Button's ::before becoming its press layer, and no engineering owner has approved the record for this head. 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), or says what in the record should change. Separately from that decision:

  • Land the earlier pull requests in the stack, or retarget this one, so the diff under review is this change's 1,064 lines instead of 29,396.
  • Fix FR7's citation of spec:AST-090, which does not exist in the repository.
  • Say in the changesets that a caller's ::before on Button now merges with the press layer and stays at opacity: 0, and that an iPad mini in portrait switches from the bottom sheet to the popover.

[Automated review]

follows.
- **FR7 — Exits animate as UIKit's do.** A touch presentation MUST play the
exit its UIKit counterpart plays. **Proposed; pending owner review:** this
departs from the draft `spec:AST-090` FR1 (exits off by default) and FR10

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 spec:AST-090 exists in docs/specs at this head or on main, so an owner can't read the FR1 and FR10 this amendment departs from. Cite the record's real id and path, or drop the clause until that record lands.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This citation is in the AST-094 record from #8052, which this PR stacks on. The finding has gone to that record's owner.

Comment thread .changeset/press-release-compositor.md Outdated
'@astryxdesign/core': patch
---

[fix] A button's or a card's touch press release now runs on the compositor: it keeps fading while the page's script is busy instead of freezing at full strength and snapping off, and it paints nothing per frame. The release follows UIButton's highlight release (250 ms, ease-in-out); a list row's stays on the deselect clock. The pressed colour and the mouse states are unchanged.

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 doesn't tell callers that Button's ::before is now the press layer, with isolation: isolate on the button. StyleX merges xstyle per property, so a caller's ::before keeps the layer's opacity: 0, z-index: -1 and inset: 0, and is invisible at rest unless it sets opacity. Worth saying here and in Button's docs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4dc4418: the changeset and Button's docs now say that Button paints its touch press on its own ::before and sets isolation: isolate. A ::before styled through xstyle merges into that layer, which rests at opacity: 0; set its opacity or style a child element instead. Moving the layer to an element Button owns stays open in the owner review named in the body.

Comment thread .changeset/touch-presentation.md Outdated
'@astryxdesign/core': patch
---

[feat] `presentation="adaptive"` on DropdownMenu, ContextMenu, MoreMenu, Selector and MultiSelector chooses the bottom sheet on a compact touch screen: a coarse primary pointer and a viewport narrower than 744px or shorter than 500px (it was 768px wide and below). An iPad mini in portrait keeps the anchored popover; a phone in landscape now gets the sheet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

On the base, (max-width: 768px) and (pointer: coarse) gives a 744 px portrait iPad mini the bottom sheet. The new (width < 744px) and (pointer: coarse), (height < 500px) and (pointer: coarse) gives it the popover, so it switches rather than keeps the popover. This is also Selector's default.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This changeset belongs to #8062 (the touch presentation resolver), which this PR stacks on. The finding has gone to that PR's owner.

@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) -
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.0KB N/A N/A 20B N/A
core/AlertDialog 57.0KB 60.2KB +3.1KB ⚠️ 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.5KB ⚠️ 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.1KB ⚠️ 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.1KB ⚠️ 9.2KB 10.0KB +806B
core/ButtonGroup 8.4KB 8.8KB +388B 3.3KB 3.5KB +264B
core/Calendar 55.1KB 58.2KB +3.1KB ⚠️ 10.0KB 10.8KB +801B
core/Card 6.4KB 6.8KB +376B 6.3KB 6.6KB +267B
core/Carousel 50.4KB 53.5KB +3.1KB ⚠️ 10.2KB 11.0KB +805B
core/Center 4.2KB 4.5KB +384B 4.6KB 4.8KB +266B
core/ChatComposer 60.8KB 63.9KB +3.1KB ⚠️ 10.8KB 11.6KB +805B
core/ChatComposerDrawer 24.0KB 24.4KB +414B 4.5KB 4.8KB +254B
core/ChatComposerInput 55.9KB 59.0KB +3.1KB ⚠️ 10.1KB 10.9KB +808B
core/ChatComposerTokenElement 55.9KB 59.0KB +3.1KB ⚠️ 10.1KB 10.9KB +808B
core/ChatDictationButton 46.6KB 49.8KB +3.1KB ⚠️ 9.5KB 10.3KB +805B
core/ChatLayout 49.0KB 52.1KB +3.2KB ⚠️ 9.9KB 10.7KB +803B
core/ChatLayoutScrollButton 46.5KB 49.7KB +3.1KB ⚠️ 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.1KB ⚠️ 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.6KB +2.5KB ⚠️ 9.3KB 9.5KB +237B
core/CheckboxList 46.2KB 46.5KB +371B 8.1KB 8.4KB +237B
core/CheckboxListItem 54.2KB 57.3KB +3.1KB ⚠️ 10.5KB 11.2KB +767B
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.2KB +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.6KB ⚠️ 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 10.9KB +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.2KB +3.5KB ⚠️ 14.6KB 15.8KB +1.1KB ⚠️
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.3KB +3.5KB ⚠️ 14.7KB 15.9KB +1.2KB ⚠️
core/DropdownMenuCheckboxItem 16.1KB 19.1KB +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 +2.9KB ⚠️ 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.4KB +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.1KB ⚠️ 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.2KB ⚠️ 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.1KB ⚠️ 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 56.7KB +3.0KB ⚠️ 10.5KB 11.2KB +767B
core/Markdown 144.5KB 147.7KB +3.2KB ⚠️ 17.4KB 18.5KB +1.1KB ⚠️
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.2KB +3.1KB ⚠️ 10.1KB 10.8KB +809B
core/MobileNavToggle 46.4KB 49.5KB +3.1KB ⚠️ 9.4KB 10.2KB +803B
core/MoreMenu 73.2KB 76.6KB +3.4KB ⚠️ 14.7KB 15.9KB +1.2KB ⚠️
core/MultiSelector 73.1KB 76.1KB +3.1KB ⚠️ 14.6KB 15.4KB +796B
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.2KB +2.4KB ⚠️ 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.2KB +3.3KB ⚠️ 15.3KB 16.4KB +1.0KB ⚠️
core/Popover 50.0KB 52.9KB +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.3KB +3.0KB ⚠️ 9.7KB 10.3KB +626B
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.1KB +2.5KB ⚠️ 3.9KB 4.1KB +257B
core/SelectableCard 12.8KB 15.7KB +3.0KB ⚠️ 7.1KB 7.9KB +813B
core/Selector 76.5KB 79.8KB +3.3KB ⚠️ 15.0KB 16.0KB +1.0KB ⚠️
core/SelectorOption 14.5KB 17.6KB +3.1KB ⚠️ 5.1KB 5.7KB +656B
core/SideNav 58.1KB 61.3KB +3.1KB ⚠️ 10.6KB 11.3KB +809B
core/SideNavCollapseButton 46.8KB 49.9KB +3.1KB ⚠️ 9.5KB 10.2KB +802B
core/SideNavHeading 54.9KB 57.9KB +3.0KB ⚠️ 10.1KB 10.9KB +809B
core/SideNavItem 54.6KB 57.8KB +3.1KB ⚠️ 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.5KB ⚠️ 9.3KB 9.6KB +234B
core/Tab 9.7KB 12.2KB +2.5KB ⚠️ 4.1KB 4.3KB +253B
core/TabList 40.3KB 40.7KB +378B 7.2KB 7.4KB +244B
core/TabMenu 51.8KB 54.9KB +3.1KB ⚠️ 10.0KB 10.8KB +801B
core/Table 83.9KB 87.3KB +3.4KB ⚠️ 15.1KB 16.3KB +1.1KB ⚠️
core/TableBody 3.3KB 3.6KB +375B 3.2KB 3.4KB +264B
core/TableCell 75.8KB 79.2KB +3.5KB ⚠️ 14.8KB 16.0KB +1.1KB ⚠️
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.0KB +3.5KB ⚠️ 14.8KB 16.0KB +1.2KB ⚠️
core/TableRow 6.2KB 6.6KB +380B 3.5KB 3.8KB +259B
core/TableSelectionToolbar 52.5KB 55.6KB +3.1KB ⚠️ 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.8KB +3.1KB ⚠️ 10.5KB 11.3KB +808B
core/Thumbnail 50.8KB 54.0KB +3.1KB ⚠️ 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 +485B 9.5KB 10.3KB +804B
core/Toast 60.0KB 63.1KB +3.1KB ⚠️ 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.8KB ⚠️ 16.5KB 17.3KB +804B
core/Toolbar 39.9KB 40.3KB +401B 7.7KB 8.0KB +273B
core/TopNav 52.0KB 55.2KB +3.2KB ⚠️ 10.4KB 11.2KB +819B
core/TopNavHeading 54.3KB 57.3KB +3.0KB ⚠️ 9.9KB 10.7KB +816B
core/TopNavItem 9.0KB 11.6KB +2.5KB ⚠️ 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.6KB +2.5KB ⚠️ 4.1KB 4.3KB +261B
core/TopNavMenu 53.7KB 56.8KB +3.0KB ⚠️ 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.1KB N/A N/A 773B N/A
lab/ChatReactionBar N/A 68.6KB 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.0KB N/A N/A 1.2KB N/A
lab/CommentThread N/A 219.5KB N/A N/A 2.4KB N/A
lab/Comments N/A 223.9KB 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.2KB 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.0KB 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.2KB N/A N/A 2.6KB N/A
richtext/RichTextEditorToolbar N/A 215.9KB N/A N/A 454B N/A
richtext/RichTextView N/A 254.5KB 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.1KB ⚠️ +805B
core/AppShell +2.9KB ⚠️ +811B
core/AvatarGroupOverflow +2.5KB ⚠️ +234B
core/Banner +3.1KB ⚠️ +807B
core/BaseTypeahead +3.1KB ⚠️ +816B
core/BreadcrumbItem +3.5KB ⚠️ +1.0KB ⚠️
core/Button +3.1KB ⚠️ +806B
core/Calendar +3.1KB ⚠️ +801B
core/Carousel +3.1KB ⚠️ +805B
core/ChatComposer +3.1KB ⚠️ +805B
core/ChatComposerInput +3.1KB ⚠️ +808B
core/ChatComposerTokenElement +3.1KB ⚠️ +808B
core/ChatDictationButton +3.1KB ⚠️ +805B
core/ChatLayout +3.2KB ⚠️ +803B
core/ChatLayoutScrollButton +3.1KB ⚠️ +805B
core/ChatSendButton +3.1KB ⚠️ +804B
core/CheckboxInput +2.5KB ⚠️ +237B
core/CheckboxListItem +3.1KB ⚠️ +767B
core/ClickableCard +3.0KB ⚠️ +813B
core/CodeBlock +3.1KB ⚠️ +819B
core/Collapsible +2.5KB ⚠️ +260B
core/CommandPalette +2.6KB ⚠️ +236B
core/CommandPaletteItem +2.5KB ⚠️ +264B
core/ComplexSelector +3.2KB ⚠️ +799B
core/ContextMenu +3.5KB ⚠️ +1.1KB ⚠️
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.2KB ⚠️
core/DropdownMenuCheckboxItem +3.0KB ⚠️ +637B
core/DropdownMenuItem +3.0KB ⚠️ +657B
core/DropdownMenuRadioItem +3.0KB ⚠️ +637B
core/DropdownMenuSubMenu +2.9KB ⚠️ +631B
core/FileInput +3.2KB ⚠️ +795B
core/IconButton +3.1KB ⚠️ +806B
core/Item +3.2KB ⚠️ +659B
core/LayerProvider +3.1KB ⚠️ +812B
core/Lightbox +3.1KB ⚠️ +808B
core/Link +2.4KB ⚠️ +237B
core/ListItem +3.0KB ⚠️ +767B
core/Markdown +3.2KB ⚠️ +1.1KB ⚠️
core/MobileNav +3.1KB ⚠️ +809B
core/MobileNavToggle +3.1KB ⚠️ +803B
core/MoreMenu +3.4KB ⚠️ +1.2KB ⚠️
core/MultiSelector +3.1KB ⚠️ +796B
core/NumberInput +3.2KB ⚠️ +805B
core/Outline +2.4KB ⚠️ +261B
core/Pagination +3.3KB ⚠️ +1.0KB ⚠️
core/Popover +2.9KB ⚠️ +797B
core/PowerSearch +3.4KB ⚠️ +1.0KB ⚠️
core/RadioListItem +3.0KB ⚠️ +626B
core/SegmentedControlItem +2.5KB ⚠️ +257B
core/SelectableCard +3.0KB ⚠️ +813B
core/Selector +3.3KB ⚠️ +1.0KB ⚠️
core/SelectorOption +3.1KB ⚠️ +656B
core/SideNav +3.1KB ⚠️ +809B
core/SideNavCollapseButton +3.1KB ⚠️ +802B
core/SideNavHeading +3.0KB ⚠️ +809B
core/SideNavItem +3.1KB ⚠️ +812B
core/Step +2.5KB ⚠️ +252B
core/Stepper +3.2KB ⚠️ +803B
core/Switch +2.5KB ⚠️ +234B
core/Tab +2.5KB ⚠️ +253B
core/TabMenu +3.1KB ⚠️ +801B
core/Table +3.4KB ⚠️ +1.1KB ⚠️
core/TableCell +3.5KB ⚠️ +1.1KB ⚠️
core/TableHeaderCell +3.5KB ⚠️ +1.2KB ⚠️
core/TableSelectionToolbar +3.1KB ⚠️ +812B
core/TextInput +3.1KB ⚠️ +808B
core/Thumbnail +3.1KB ⚠️ +800B
core/TimeInput +3.2KB ⚠️ +799B
core/Toast +3.1KB ⚠️ +806B
core/ToggleButton +3.1KB ⚠️ +806B
core/Token +2.5KB ⚠️ +262B
core/Tokenizer +2.8KB ⚠️ +804B
core/TopNav +3.2KB ⚠️ +819B
core/TopNavHeading +3.0KB ⚠️ +816B
core/TopNavItem +2.5KB ⚠️ +261B
core/TopNavMegaMenu +3.1KB ⚠️ +810B
core/TopNavMegaMenuItem +2.5KB ⚠️ +261B
core/TopNavMenu +3.0KB ⚠️ +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 35.7KB +1.2KB

Accessibility Audit

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

Button pattern - 1 issue(s)
  • 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
    • Rule: color-contrast · Affects 1/10 stories · Learn more
    • WCAG: 1.4.3 (Level AA)
Checkbox pattern - 1 issue(s)
  • 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
    • Rule: color-contrast · Affects 1/3 stories · Learn more
    • WCAG: 1.4.3 (Level AA)
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/14 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 engineering-owner decision: the press changes rest on draft spec:AST-094 (DEC-1, FR5, FR11's 250 ms ease-in-out control release, FR13), and Button's ::before becoming its press layer has no current spec. Any engineering owner listed in .github/ENGOWNERS records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Whatever the decision, please address the points below.

  • Narrow the diff to this change (1,064 of the 29,399 authored lines) by landing #8117 and its predecessors or retargeting onto its branch.
  • .changeset/press-release-compositor.md and .changeset/list-row-press-compositor.md should say a caller's own ::before merges into the press layer and stays hidden at rest, not that it replaces the press paint.
  • .changeset/touch-presentation.md should say an iPad mini in portrait now gets the anchored popover instead of the sheet.
  • spec:AST-094 FR7 cites spec:AST-090, which is not on main.

[Automated review]

Comment thread .changeset/press-release-compositor.md Outdated
'@astryxdesign/core': patch
---

[fix] A button's or a card's touch press release now runs on the compositor: it keeps fading while the page's script is busy instead of freezing at full strength and snapping off, and it paints nothing per frame. The release follows UIButton's highlight release (250 ms, ease-in-out); a list row's stays on the deselect clock. The pressed colour and the mouse states are unchanged.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Button's ::before now carries the press layer (and Button gains isolation: isolate). A caller who styles Button's ::before through xstyle keeps the layer's opacity: 0, inset: 0 and z-index: -1, so their content is hidden until the button is pressed. Please say so here, with the migration a released styling change needs, or move the layer to an element Button owns.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4dc4418: the changeset and Button's docs now say that Button paints its touch press on its own ::before and sets isolation: isolate. A ::before styled through xstyle merges into that layer, which rests at opacity: 0; set its opacity or style a child element instead. Moving the layer to an element Button owns stays open in the owner review named in the body.

Comment thread .changeset/list-row-press-compositor.md Outdated
'@astryxdesign/core': patch
---

[fix] A list row's touch press release keeps fading while the page runs script. `ListItem`, `Item` and `TreeList` rows used to stay fully pressed after the finger lifted until the main thread was free, and every frame of the fade repainted the row; the release now runs on the compositor, with the same color, clock and curve as before. On an interactive row the press paints on the row's `::before`, so a row's own `::before` set through `xstyle` or `className` replaces the press paint.

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 row's own ::before does not replace the press paint: StyleX merges ::before property by property, so an xstyle ::before keeps the layer's opacity: 0 and is hidden at rest, and a className rule can at most override the properties it sets.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This changeset belongs to #8077 (list rows), which this PR stacks on. The finding has gone to that PR's owner.

Comment thread .changeset/touch-presentation.md Outdated
'@astryxdesign/core': patch
---

[feat] `presentation="adaptive"` on DropdownMenu, ContextMenu, MoreMenu, Selector and MultiSelector chooses the bottom sheet on a compact touch screen: a coarse primary pointer and a viewport narrower than 744px or shorter than 500px (it was 768px wide and below). An iPad mini in portrait keeps the anchored popover; a phone in landscape now gets the sheet.

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 744 x 1133 with a coarse pointer the base matched (max-width: 768px) and (pointer: coarse) and opened the bottom sheet; this head opens the anchored popover. Suggest: "An iPad mini in portrait now gets the anchored popover instead of the sheet."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This changeset belongs to #8062 (the touch presentation resolver), which this PR stacks on. The finding has gone to that PR's owner.

follows.
- **FR7 — Exits animate as UIKit's do.** A touch presentation MUST play the
exit its UIKit counterpart plays. **Proposed; pending owner review:** this
departs from the draft `spec:AST-090` FR1 (exits off by default) and FR10

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-090 is not on main (no docs/specs/AST-090* record), so an approver cannot see which FR1 and FR10 this departs from. Please cite the record that holds them, or drop the clause; line 235 repeats the citation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This citation is in the AST-094 record from #8052, which this PR stacks on. The finding has gone to that record's owner.

@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 is on hold for a spec decision, and three fixes are needed either way. Approval needs an engineering owner to approve the draft spec:AST-094 direction this change rests on (UIKit as the touch reference, releases on the compositor, and UIButton's 250 ms ease-in-out control release, which amends design:user-states DR1/DR3) and Button's ::before becoming its press layer. An engineering owner records that decision with /approve-spec <full head SHA> (the author may, as an engineering owner), or says which part should change.

  • Land #8117 and its predecessors first, or retarget onto its branch: this pull request carries 31,222 authored lines.
  • Correct the changesets: touch-presentation.md says an iPad mini in portrait keeps the popover, but it moves from the sheet to the popover; list-row-press-compositor.md and press-release-compositor.md misdescribe or omit how a caller's ::before merges into the press layer.
  • Fix the citation of spec:AST-090 in spec:AST-094 FR7 and its impact list; no such record exists.

[Automated review]

@vjeux
vjeux force-pushed the uikit/c01-press-release branch from add4faf to 4dc4418 Compare October 24, 2026 21:59
@vjeux

vjeux commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

On the diff size: this PR is stacked on #8117 and the foundation PRs listed in its stack line. Only its last commit, 4dc4418, is this change; the rest of the diff is its predecessors. It narrows as they land in the stated order.

@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 is on hold for an engineering-owner decision, and several fixes are needed either way. The press changes rest on the draft spec:AST-094 (UIKit as the touch reference, releases on the compositor, and UIButton's 250 ms ease-in-out release for every non-cell touch press), and no record yet governs Button's ::before press layer or the new usePressFeedback profile option, holdSelection and pressLayer.stylex exports: an engineering owner approves or amends that direction by commenting /approve-spec <full head SHA> on this pull request (the author may, as an engineering owner) and names the record for the layer and the exports.

  • Under a finger a RadioList row now paints the whole row instead of its circle, which design:user-states DR1 rules out, so the indicator fix from #8233 has to land in this pull request or before it.
  • The new compact test ((width < 744px) in touchPresentation.ts) contradicts spec:AST-012 FR9: an iPad mini in portrait loses the bottom sheet, so build it from the theme's compact definition.
  • Land #8117 and its predecessors first, or retarget onto its branch, since this pull request carries 31,362 authored lines.
  • Correct the changesets (the iPad mini line in touch-presentation.md, "replaces" in list-row-press-compositor.md, and the missing className case and other controls' new clock in press-release-compositor.md) and the spec:AST-090 citation in spec:AST-094 FR7.

[Automated review]

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.

Current spec:AST-012 FR9 and public-component-api INV17 define compact as width below the theme's effective md point (768 by default) or height below 500, and say no resolver keeps numbers of its own. With (width < 744px), coarse-pointer screens 744-767 px wide (an iPad mini in portrait) now get the anchored popover from Selector, MultiSelector, DropdownMenu, ContextMenu and MoreMenu where FR9 gives the bottom sheet. Build this from the theme's compact definition instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This resolver code comes from #8062 in the stack, not this pull request. Its owner has taken the finding: #8062 now reads the theme's compact definition (398c723, "the touch resolver reads the theme's compact definition").

Comment thread packages/core/src/Item/Item.tsx Outdated
// The touch press on a compositor layer (after the overlay, whose
// own touch paint it turns off): the release keeps fading while
// script runs.
isInteractive && pressLayerStyles.cell,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

With the press on this ::before cell layer, a RadioList row no longer paints its circle: RadioListItem suppresses Item's background colour and image but not this layer, and the circle reads --_press-paint-image, which the layer turns transparent. Under a finger the whole row paints instead, against design:user-states DR1 ("RadioList · Indicator paints --color-overlay-pressed"). The fix in #8233 depends on this pull request, so it needs to land with this change rather than after it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Kept as a separate pull request, one intent each: #8233 gives the RadioList and CheckboxList indicators their own press layer and lands with or right after this one. Both bodies state that order.

Comment thread .changeset/press-release-compositor.md Outdated
'@astryxdesign/core': patch
---

[fix] A button's or a card's touch press release now runs on the compositor: it keeps fading while the page's script is busy instead of freezing at full strength and snapping off, and it paints nothing per frame. The release follows UIButton's highlight release (250 ms, ease-in-out); a list row's stays on the deselect clock. The pressed colour and the mouse states are unchanged. Button now paints the touch press on its own `::before` and sets `isolation: isolate`: a `::before` you style through `xstyle` merges into that layer, which rests at `opacity: 0`, so set its `opacity` or style a child element instead.

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 names only xstyle, but a className rule on Button's ::before also keeps the layer's opacity: 0 and z-index: -1 and is hidden at rest. It also covers "a button's or a card's" release, while the overlay's default arm moves the other control surfaces (Switch, Link, Tab, CheckboxInput and more) to 250 ms ease-in-out as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4f42af6: the changeset names a className rule as well as xstyle (the layer rests at opacity: 0 and z-index: -1), and says every control on the shared overlay, Switch, Link, Tab and CheckboxInput included, now releases on UIButton's 250 ms ease-in-out.

vjeux added 22 commits October 29, 2026 12:16
…esture host's subscriptions

The gesture host subscribes no click: the one cancelling click subscriber
is the click guard's, added at a window's first arm and kept. The release
note, the stream's header and the gestures README listed click with the
host's touchend and contextmenu; they now name the guard's own.
…tion constant

From review on meta#8890: spec:AST-094 FR4 lists the 200 ms Reduce Motion cap
among the touch motion roles and says a component never plays a literal.
The release capped its clock at a literal 200 in pressFeedback.ts
(REDUCED_MOTION_MAX_MS) and at a literal 200ms in the overlay's reduced
release duration. Both now read the shared constant: the controller
timing.reducedMotionCrossFadeMs, the stylesheet
reducedMotionFadeConsts.duration. Both are 200 ms, so nothing on screen
changes; the built stylesheet's cap is still 200 ms.

Red first: a unit case that stubs timing.reducedMotionCrossFadeMs at
120 ms found the release still fading at 120 ms on the change before
this one; it now ends there.
…Motion constant too

The row press layer capped its release under reduced motion at a literal
200ms (CELL_RELEASE_REDUCED), the same cap the review on meta#8890 asked the
overlay to read from the shared constant (spec:AST-094 FR4: the 200 ms
Reduce Motion cap is a role, never a literal). It now reads
reducedMotionFadeConsts.duration, as the overlay does; both are 200 ms,
and the built stylesheet's cap is still 200 ms.

A unit case holds every reduced-motion cap in the layer and the overlay to
the constant. Red first: it found the layer's literal on the change before
this one.
…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.
…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.

@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 is on hold for an engineering-owner decision on the rewritten component:Button FR15, and some changes are needed either way. Current FR15 says a finger's press fades out over 200 ms and is instant under reduced motion; this pull request paints it on Button's own ::before and fades it over 250 ms under a coarse pointer (a 200 ms cross-fade under Reduce Motion) and gives every other control on the shared overlay the same 250 ms release, so an engineering owner needs to approve or amend that text by commenting /approve-spec <full head SHA> on this pull request, which the author may do as an engineering owner. Separately:

  • Please land #8117, #8338 and #8350 first and rebase: the diff carries 32,770 authored lines, and this change's own 2,400 still need a split or a justification.
  • FR15 groups a pen with the mouse, but the press controller runs a pen as a finger, so on a tablet it gets the 250 ms layer release; please make the two agree and add a pen case to the timing spec.
  • The stacked gesture host still attaches nothing where the primary pointer is fine, which current spec:AST-094 FR19 and FR43 rule out; that fix belongs in the gesture pull request.

[Automated review]

| FR12 | With `tooltip`, the tooltip attaches to the Button element itself (no wrapper element), opens above it, and its content is added to `aria-describedby` after any caller value. | Current source; real-browser accessibility test |
| FR13 | A standalone Button paints the shadow token for its `elevation`. Inside a ButtonGroup it paints no shadow and reflects `data-elevation="none"`, whatever `elevation` says. | `family:buttons` FR8–FR10; current source and tests |
| FR14 | The root carries `astryx-button` and reflects the effective `data-variant`, `data-size`, and `data-elevation`. | `family:buttons` FR10; `architecture:component-theming-surface`; tests |
| FR15 | Hover paints the shared interaction overlay on hover-capable pointers, and a press paints the shared pressed overlay through the press controller: at once under a mouse or a pen (`spec:AST-094` FR11's fine-pointer profile) and on FR11's control profile under a finger, fading out over 200 ms on release at `tempo` 1, scaled by the theme's `tempo` (`spec:AST-108` FR4). Under a coarse primary pointer the overlay paints on Button's own `::before`, present while pressed and through its fade-out, and a touch press fades over UIButton's measured release instead (250 ms at `tempo` 1, `timing.press.buttonReleaseMs`); a Space press keeps the 200 ms fade, as every press does with a fine primary pointer (`spec:AST-017` FR59). Without a reduced-motion preference, a standalone Button also scales to 0.98 on a mouse press, or while the touch press reads on; under reduced motion it MUST NOT scale (`design:reduced-motion` DR1). All of this paint follows the element's exposed disabled state: a Button that is natively disabled or exposes `aria-disabled="true"` (busy-only, disabled with a tooltip, or a caller's own `aria-disabled`) shows no overlay and no press scale, even when it stays operable. Inside a ButtonGroup the press scale does not apply. Button's hover and press transitions are fast. Under reduced motion they are instant with a fine primary pointer, and a touch release under a coarse primary pointer cross-fades within 200 ms instead (`design:reduced-motion` DR2, DR3). Implementation gap: the 0.98 press scale also applies under reduced motion; only its transition becomes instant. | Current source and tests; `spec:AST-094` FR11 and DEC-21 for the mouse and pen press |

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 rewrites current FR15, which says a finger's press fades "out over 200 ms on release" and that press transitions are "instant under reduced motion": under a coarse primary pointer the press now paints on Button's own ::before, present through its fade-out, a touch press fades over 250 ms, and a reduced-motion touch release cross-fades within 200 ms. An engineering owner needs to approve or amend it by commenting /approve-spec <full head SHA>. Two points ride on it: the sentence keeps a pen with the mouse, while the controller presses a pen as a finger, and "250 ms at tempo 1" implies a tempo scaling the fixed 250ms in controlPressLayer.ts does not do (main's 200 ms has the same gap).

Comment on lines +693 to +698
// A mouse or a key keeps the fine pointer's 200 ms release on any surface;
// the marker tells the stylesheet's release arms the same.
element.setAttribute(
PRESS_INPUT_ATTRIBUTE,
pointerId == null ? 'key' : 'mouse',
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Only pressButton marks a press, and onPointerDown sends every pointer that is not a mouse down the finger path (lines 722-777), so on a tablet a pen gets Button's ::before layer and the 250 ms release, while the rewritten FR15 groups the pen with the mouse. Either route pointerType === 'pen' here with its own marker so it keeps 200 ms, or say in FR15 that a pen on a coarse primary pointer presses as a finger, and add the pen case to ButtonPressTiming.

Comment on lines +12 to +22
* ONLY WHERE THE PRIMARY POINTER IS COARSE
*
* - Recognizers attach, and the host registers listeners, only while the
* primary pointer is coarse (`spec:AST-094` FR19, FR24; coarsePointer.ts).
* Where it is fine, an attach is recorded but nothing is installed: no
* document, window or element listener, no `touch-action` or other style,
* nothing delivered, so a finger on a touchscreen laptop gets the
* browser's defaults. The host follows the query: turning coarse installs
* it and styles every registration; turning fine cancels every touch in
* flight and removes it all. A development warning names the first attach
* under a fine pointer.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Current spec:AST-094 FR19 says "Recognizers attach for every pointer, with FR43's exceptions", and FR43 that gestures attach for "a mouse, a pen, and a finger, not only a coarse primary pointer"; this host installs nothing where the primary pointer is fine, and cites FR19 for it. No shipped component attaches the host in this diff, but the fix belongs in the gesture pull request before this one rebases onto it.

vjeux added 6 commits October 29, 2026 18:21
…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]

@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 is on hold for an engineering-owner decision on the rewritten component:Button FR15, and some changes are needed either way. Current FR15 says a finger's press fades out over 200 ms and is instant under reduced motion, while this pull request paints it on Button's own ::before, fades it over 250 ms under a coarse pointer (a 200 ms cross-fade under Reduce Motion) and gives every other control on the shared overlay the same 250 ms release, so an engineering owner needs to approve or amend that text by commenting /approve-spec <full head SHA> on this pull request, which the author may do as an engineering owner. Separately:

  • Please land #8360, #8890, #8077, #8117 and #8350 first and rebase: the diff carries 33,092 authored lines, and this change's own 2,570 still need a split or a justification.
  • FR15 groups a pen with the mouse, but the press controller runs a pen as a finger, so on a tablet it gets the 250 ms layer release; please make the two agree and add a pen case to the timing spec.
  • The stacked gesture host still attaches nothing where the primary pointer is fine, which current spec:AST-094 FR19 and FR43 rule out; that fix belongs in the gesture pull request.

[Automated review]

| FR12 | With `tooltip`, the tooltip attaches to the Button element itself (no wrapper element), opens above it, and its content is added to `aria-describedby` after any caller value. | Current source; real-browser accessibility test |
| FR13 | A standalone Button paints the shadow token for its `elevation`. Inside a ButtonGroup it paints no shadow and reflects `data-elevation="none"`, whatever `elevation` says. | `family:buttons` FR8–FR10; current source and tests |
| FR14 | The root carries `astryx-button` and reflects the effective `data-variant`, `data-size`, and `data-elevation`. | `family:buttons` FR10; `architecture:component-theming-surface`; tests |
| FR15 | Hover paints the shared interaction overlay on hover-capable pointers, and a press paints the shared pressed overlay through the press controller: at once under a mouse or a pen (`spec:AST-094` FR11's fine-pointer profile) and on FR11's control profile under a finger, fading out over 200 ms on release at `tempo` 1, scaled by the theme's `tempo` (`spec:AST-108` FR4). Under a coarse primary pointer the overlay paints on Button's own `::before`, present while pressed and through its fade-out, and a touch press fades over UIButton's measured release instead (250 ms at `tempo` 1, `timing.press.buttonReleaseMs`); a Space press keeps the 200 ms fade, as every press does with a fine primary pointer (`spec:AST-017` FR59). Without a reduced-motion preference, a standalone Button also scales to 0.98 on a mouse press, or while the touch press reads on; under reduced motion it MUST NOT scale (`design:reduced-motion` DR1). All of this paint follows the element's exposed disabled state: a Button that is natively disabled or exposes `aria-disabled="true"` (busy-only, disabled with a tooltip, or a caller's own `aria-disabled`) shows no overlay and no press scale, even when it stays operable. Inside a ButtonGroup the press scale does not apply. Button's hover and press transitions are fast. Under reduced motion they are instant with a fine primary pointer, and a touch release under a coarse primary pointer cross-fades within 200 ms instead (`design:reduced-motion` DR2, DR3). Implementation gap: the 0.98 press scale also applies under reduced motion; only its transition becomes instant. | Current source and tests; `spec:AST-094` FR11 and DEC-21 for the mouse and pen press |

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 rewrites current FR15, which says a finger's press fades "out over 200 ms on release" and that press transitions are "instant under reduced motion": under a coarse primary pointer the press now paints on Button's own ::before through its fade-out, a touch press fades over 250 ms, and a touch release under Reduce Motion cross-fades within 200 ms. An engineering owner needs to approve or amend this text by commenting /approve-spec <full head SHA>; the pen also needs settling (see the comment on pressFeedback.ts).

Comment on lines +705 to +710
// A mouse or a key keeps the fine pointer's 200 ms release on any surface;
// the marker tells the stylesheet's release arms the same.
element.setAttribute(
PRESS_INPUT_ATTRIBUTE,
pointerId == null ? 'key' : 'mouse',
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Only pressButton writes this marker, and onPointerDown sends every pointer that is not a mouse down the finger path (lines 734-789), so on a tablet a pen gets the ::before layer and the 250 ms release while FR15 groups the pen with the mouse. Either route pointerType === 'pen' here with its own marker so it keeps 200 ms, or say in FR15 that a pen on a coarse primary pointer presses as a finger, and add a pen case to ButtonPressTiming.interaction.browser.spec.ts.

Comment on lines +12 to +22
* ONLY WHERE THE PRIMARY POINTER IS COARSE
*
* - Recognizers attach, and the host registers listeners, only while the
* primary pointer is coarse (`spec:AST-094` FR19, FR24; coarsePointer.ts).
* Where it is fine, an attach is recorded but nothing is installed: no
* document, window or element listener, no `touch-action` or other style,
* nothing delivered, so a finger on a touchscreen laptop gets the
* browser's defaults. The host follows the query: turning coarse installs
* it and styles every registration; turning fine cancels every touch in
* flight and removes it all. A development warning names the first attach
* under a fine pointer.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Current spec:AST-094 FR19 says "Recognizers attach for every pointer, with FR43's exceptions", and FR43 that gestures attach for "a mouse, a pen, and a finger, not only a coarse primary pointer"; this header, refresh and applyStyle install nothing where the primary pointer is fine. This is #8360's code, so it needs fixing there before this pull request rebases onto it.

This branch was successfully deployed

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