Skip to content

fix(Item): a list row's touch press release runs on the compositor - #9762

Merged
cixzhang merged 1 commit into
mainfrom
fix/list-row-press-compositor
Nov 4, 2026
Merged

cixzhang merged 1 commit into
mainfrom
fix/list-row-press-compositor

Conversation

@cixzhang

@cixzhang cixzhang commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Supersedes #8077 by @vjeux, rebased onto main and stacked on #9758 (which supersedes #8890). The change is vjeux's: his commit authorship is kept, and the changeset credits him. This pull request targets #9758's branch so only its own change shows. It moves to main once #9758 merges.

User impact

This affects people who tap a list row on a phone: a ListItem, an actionable Item (and the menus and selectors built on one), or a TreeList row.

  • Today: after the lift, a pressed row should fade over 200 ms. A tap usually starts work, so the main thread is busy exactly then. The release animates a gradient that only the main thread can run, so the row stays fully pressed while script runs, then snaps off. Every frame of the fade also repaints the row.
  • After this change: once the release has started, the row keeps fading while the page runs script, with the same color, clock and curve. Script in the tap's own handlers, before the next frame, still holds the row pressed until it ends.
  • Unchanged: a mouse or trackpad row: no layer, no new stacking context.

Rebase onto main (after #9758 merged)

#9758 merged as b206f63, so this branch is now one commit on main. The range-diff of this pull request's own change, before (on #9758's branch) and after (on main), is CodeBunny's requested fixes and nothing else:

  • The changeset says the release keeps fading "once the release has started", and that script in the tap's own handlers still holds the row pressed.
  • The two new browser specs import from @astryxdesign/a11y-spec/playwright, as main now requires. The decoder browser's context calls keepRequestsLocal.
  • A comment in privateAttributes/press.stylex.ts says a caller's ::before "merges into" the layer while pressed. A spec comment says 200 ms, not 250 ms.

No product code changed. The List, Item, TreeList, RadioList and press utility suites pass on the rebased head, apart from two RadioListItem source-build cases that need a full install.

Problem and solution fit

Under a finger, the press now paints on a layer under the row's content (the row's ::before) and fades that layer's opacity. Engines run an opacity animation on the compositor, so it keeps advancing while the main thread is blocked. #9758 makes the press controller end the press when that fade has played, not on a clock from the lift.

The layer exists only while a press is on or releasing. At rest, a row's own ::before and its stacking are as they were. During a press, a caller's own ::before merges into the layer, so decoration that should stay through a press belongs on ::after or a child element. A pressed row's divider keeps its resting colour.

Changes since #8077

main changed under #8077 while it waited:

  • Owner module: the row layer's styles read the private press attribute. architecture:public-component-api INV23 and the no-raw-private-attribute rule now keep such styles in the attribute's owner module, so utils/rowPressLayer.ts becomes privateAttributes/press.stylex.ts. The styles are unchanged. Its clocks stay in utils/rowPressLayer.stylex.ts.
  • Tempo: on main the strength's release follows the theme's tempo, capped under Reduce Motion (spec:AST-108 FR4). The row layer now plays that same clock, the same way Switch's press layer does, so the layer and the strength land together (FR4a). interactionOverlay.cell.test.ts now checks that both use one clock, where it used to check the old Reduce Motion constant.
  • Radio rows: a radio row keeps no press paint of its own, because its circle paints the press. It composes the owner module's cellWithoutLayer, which carries the row's empty image, its strength release and the absence of a ::before box and stacking context, so RadioListItem sets none of those beside cell (INV23). The radio-row interaction case checks the row is no stacking context. swipeRoot also drops isolation, because its transform already isolates the row.
  • WebKit: WebKit can start the release at the time busy script began, so its first frame after a block can already be near rest (opacity 0.03 measured). "A busy first frame after the lift delays the release instead of skipping into it" therefore skips WebKit, and "…still plays the whole release before the press attribute goes" asserts only the end and the order of frames there.
  • No list table: fix(Item): a list row's touch press release runs on the compositor #8077 added motion/uikit/list.ts, which only re-exported timing.press. It is dropped with its index, README and MEASUREMENTS rows, and cellPressConsts is held to timing.press directly. Record citations left the source comments.

Expected behavior and authority

usePressFeedback ("fades the release over 200 ms"); component:List FR11; spec:AST-094 FR5 and FR13; design:user-states DR1 and DR5. A mouse row is unchanged (spec:AST-017 FR59).

Record change, component:List: the owner decided on 2026-10-25 that on touch, list rows may use ::before as their press layer, and a pressed row's divider keeps its resting colour. Specs & Decisions gave the record text:

  • FR11 gains: "Under the touch presentation, an interactive Item, ListItem or TreeList row uses its ::before as the press layer, and a pressed row's divider keeps its resting colour. A caller's own ::before on such a row merges into the press layer only while it is pressed; use ::after or a child for decoration."
  • DEC-6 records that decision. OQ1, which asked exactly this, is removed.
  • Compatibility row: it now says the compositor release (FR11) is implemented.

Smallest restoration

  • Keep the gradient: it freezes.
  • Fade in JavaScript: it freezes the same way.
  • Chosen: an opacity layer. It is the one paint the compositor runs alone. Rows compose it from the press owner module, reading the press state utils/pressFeedback.ts writes, so there is no second press machine.

Verification

  • Unit: interactionOverlay.cell.test.ts (17 cases) and interactionOverlay.exports.test.ts are new. The stylesheet test's compositor-layer block checks the shipped CSS.
  • Suites: the utils, Item, List, TreeList, RadioList and motion suites pass, as do the stylesheet test, the eslint-plugin suites, the core type check and the private-attribute baseline check. RadioListItem's two source-build cases need @babel/preset-typescript, which my local export tree lacks; CI runs them.
  • Known cost: the row layer adds rules to every Item, so jsdom tests that read many computed styles get slower. RadioGroup.a11y.test.tsx's "runs the DOM layer" took 19.5 s here against 14.8 s on fix(core): a touch press's release lasts until its fade has played #9758's head, on the same loaded machine. Its 30 s limit holds on an idle machine. I'm noting it in case CI runs close to that limit.
  • review:pre: no findings; 1,998 authored lines against fix(core): a touch press's release lasts until its fade has played #9758's head.
  • Real Chromium (phone viewport, touch): the List row press interaction specs (ListRowPress, ListRowPressPaths), Chromium 154:
    • This head: 15/15 pass (rerun after the radio-row change).
    • fix(core): a touch press's release lasts until its fade has played #9758's head (no row layer): 8 of them fail. These are the release that keeps fading through a blocked main thread (List, Item in a scroller, TreeList), the release curve sampled frame by frame, Reduce Motion's fade, the dividers around a held row, a resting row's own ::before, and the storyboard.
    • WebKit isn't available in my local rig; CI runs it.
  • On a phone: with an iPhone or the iOS simulator, open the List "Interactive" story.
    1. Hold a row until it highlights, then lift: it fades over about 0.2 s.
    2. In Web Inspector, freeze the page for 0.7 s a frame after the next lift: addEventListener('pointerup', () => requestAnimationFrame(() => requestAnimationFrame(() => { const end = performance.now() + 700; while (performance.now() < end); })), {once: true}). Then press and lift a row: once the release has started, the highlight keeps fading through the freeze. Script that runs in the lift's own handlers, before the next frame, still holds the row pressed until it ends.
    3. With Reduce Motion on, repeat step 1: the same short fade.
    4. With a mouse, rows look and behave as before.

Scope

  • One defect is restored.
  • No new public API, default, or interaction model.
  • Public text and artifacts contain no internal Meta context.

Writing rubric results

component:List (FR11's added sentences, DEC-6, the compatibility row, OQ1 removed):

  • R1 narration: none. FR11 and DEC-6 state what a row does, in the present tense.
  • R2 research: none in the record.
  • R3 evidence: none names a pull request or commit.
  • R4 rejected alternatives: DEC-6 names one, leaving a caller's ::before intact, with its reason.
  • R5 change references: none.
  • R6 open questions: OQ1 is answered by DEC-6 and removed; OQ2 is unchanged.

@vercel

vercel Bot commented Nov 3, 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 Nov 4, 2026 9:09am UTC

Request Review

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

astracat-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 4, 2026, 9:27 AM UTC

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The compositor release for list rows holds up in Chromium: under a finger the highlight keeps fading through script, and mouse and trackpad rows are unchanged.

Approval waits on an engineering owner's decision that no current spec covers: during a touch press, a caller's own ::before on an interactive Item, ListItem or TreeList row wins over the press layer, so a decorated row's decoration jumps to the corner and the row shows no pressed fill until the release ends. 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 adds the outcome to component:List FR11.

Please also fix:

  • In WebKit, "a busy first frame after the lift delays the release instead of skipping into it" (ListRowPress.interaction.browser.spec.ts) fails on this head with opacity 0.03 against more than 0.7, so either start the WebKit release at its first presented frame or skip that case in WebKit and record the measured value.
  • The description's User impact says tables are built on Item, but Table renders neither Item nor ListItem, so please drop "tables".
  • RadioListItem.tsx sets the owner style's ::before content again on Item's root, which architecture:public-component-api INV23 rules out and which still leaves a pressed radio row as a stacking context, so export a ready-made no-layer style from privateAttributes/press.stylex.ts and spread that instead.

[Automated review]

);
// At most one frame into the release: its ease-out curve takes the first
// frame to about 0.74; a start pinned before the block would be past 0.4.
expect(first).toBeGreaterThan(0.7);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In WebKit this fails on this head: the first frame after the 150 ms block shows the layer at opacity 0.033 (3 of 3 runs), so the release skips nearly to rest instead of starting late. Either start the WebKit release at its first presented frame, or skip this case in WebKit and record the measured value, as the video case does.

Comment on lines +157 to +159
'::before': {
content: 'none',
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This sets rowPressLayerStyles.cell's ::before content again on the same element, which architecture:public-component-api INV23 rules out ("a caller does not set those properties again on the same element"). It also leaves cell's isolation, so a pressed radio row still becomes a stacking context. A ready-made no-layer style exported from privateAttributes/press.stylex.ts would keep every condition in the owner.

Comment on lines +93 to +94
* the layer is the row's `::before`, so a caller's decoration there is
* replaced for that time (decorate with `::after` or a child).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: this says a caller's ::before decoration is replaced during a press, but the shipped and tested outcome is the opposite: the caller's properties win (see the changeset). Suggest: "During a press, a caller's own ::before properties win over the layer's."

@cixzhang

cixzhang commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

/approve-spec e3a7f4a

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The compositor release for list rows holds up in Chromium: under a finger the row keeps fading through script, and mouse and trackpad rows are unchanged. The component:List FR11 and DEC-6 change has an engineering owner's approval for this head, so the remaining work is in the code and tests.

Please fix:

  • In WebKit, "a busy first frame after the lift still plays the whole release before the press attribute goes" (ListRowPress.interaction.browser.spec.ts) fails intermittently (2 of 12 runs, first frame at opacity 0.03), so either assert only what holds in WebKit and record the measured frames, as the case before it does, or start the WebKit release at its first painted frame, and update the description, which says this case keeps running there.
  • The pull request is 2,055 authored lines, over the 2,000-line limit, and motion/uikit/list.ts only re-exports timing.press, so dropping that table entry with its index, README and MEASUREMENTS rows, and holding cellPressConsts to timing.press directly, would bring it under the limit.
  • Under architecture:public-component-api INV23 a caller must not set a property again that the owner style cell sets on the same element, yet Item's swipeRoot sets isolation and the radio row sets animationName (through pressedAlpha) and backgroundImage; let cellWithoutLayer carry the radio row's strength release and empty image, and drop isolation from swipeRoot, whose transform already isolates the row.

The inline comments add two small non-blocking notes.

[Automated review]

.__frames,
);
const fading = frames.filter(([, , press]) => press === 'fading');
expect(fading.length, JSON.stringify(frames)).toBeGreaterThan(3);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In WebKit this fails intermittently on this head (2 of 12 runs). In each failure the first frame after the 150 ms block reads opacity 0.033 or 0.034, then rest, so only two or three frames are still fading. That is the curve 150 ms in: WebKit started the release when the block began, as the skip reason on the case above says. Either assert only that the release reaches rest before the attribute goes in WebKit, recording the measured frames, or start the WebKit release at its first painted frame.

Comment thread packages/core/src/motion/uikit/list.ts Outdated

import {timing} from './timing';

export const list = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

list.highlight only re-exports timing.press.highlightOutMs and highlightOutCurve, and the tests pin cellPressConsts to PRESS_FADE_MS, so a list-specific clock cannot diverge. With its index, README and MEASUREMENTS rows this entry is 68 lines, enough to bring the PR from 2,055 authored lines under 2,000. Suggest dropping it and holding cellPressConsts to timing.press directly.

// 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 && rowPressLayerStyles.cell,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

styles.swipeRoot, composed after this on swipe rows (line 929), sets isolation again over cell's, which INV23 rules out ("a caller does not set those properties again on the same element"). The result matches today, but a condition later added to cell's isolation would skip swipe rows. swipeRoot's translate3d(var(--_item-swipe-travel, 0px), 0, 0) already makes the row a stacking context, so its isolation can go.

// The row owns the strength the circle's layer reads, and its
// release (Item's touch layer drops the row's own under a finger:
// this row has no layer of its own, so it keeps the release).
!isDisabled && interactionOverlayStyles.pressedAlpha,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This sets animationName again over the owner style cell that Item spreads on the same element, and the bare look's backgroundImage: 'none' (line 156) does the same for backgroundImage; INV23 keeps those in the owner. cellWithoutLayer could carry the radio row's strength release (pressRelease on fading, under both pointers) and its empty row image, so this row drops pressedAlpha and its own backgroundImage.

* content. The layer exists only while a press is on or releasing: at rest
* a row's own `::before` and its stacking are as they were; during a press
* the layer is the row's `::before`, so a caller's decoration there is
* replaced for that time (decorate with `::after` or a child).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: this says a caller's ::before decoration is replaced during a press, but FR11 now says it "merges into the press layer" and the changeset says the caller's properties win. Suggest: "During a press, a caller's own ::before properties win over the layer's."

// to its resting color: from a frame still short of rest, it reached rest
// inside the blocked window. A release on the main thread would hold its
// first frame's value until the block ends, 700 ms on, and could not. (The
// recording's frames are 40 ms apart, so how many land inside the 250 ms

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: the release is 200 ms (RELEASE_MS), not 250 ms.

@cixzhang

cixzhang commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

/approve-spec fc57431

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Recommended for approval: under a finger, an interactive ListItem, Item or TreeList row now keeps fading its release while the page runs script, and mouse and trackpad rows are unchanged. The component:List FR11 and DEC-6 change has an engineering owner's approval for this head, and the three issues raised earlier are fixed: the WebKit busy-first-frame case now passes consistently, the change is under 2,000 lines, and cellWithoutLayer carries the radio row's image and release.

Optional comment cleanups:

  • privateAttributes/press.stylex.ts still says a caller's ::before is "replaced" during a press, while FR11 says it merges into the layer and the changeset says the caller's properties win.
  • The cell and cellWithoutLayer JSDoc blocks could keep only the reason, in shorter sentences.
  • ListRowPress.interaction.browser.spec.ts mentions "the 250 ms release"; the release is 200 ms.

[Automated review]

Comment on lines +198 to +200
* radio row, whose circle paints the press. The row paints no overlay
* image, keeps the strength's own release (the circle's layer reads the
* strength), and under a finger has no `::before` box and is no stacking

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: this second sentence runs about 30 words on three topics. Shorter, with only the reason: "The row paints no image. It keeps the strength's release, which the circle reads. Under a finger it has no ::before and no stacking context."

@cixzhang

cixzhang commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

/approve-spec 6128ad5

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Recommended for approval, pending CI: CI has not run on this head yet. Under a finger, an interactive ListItem, Item or TreeList row now keeps fading its release while the app runs script, mouse and trackpad rows are unchanged, and the component:List FR11 and DEC-6 change has an engineering owner's approval for this head.

Optional cleanups:

  • Miss: the description's phone check, step 2, freezes the page in a pointerup listener, which runs before the release starts, so the row stays pressed through the freeze and fades after it; freezing in the row's click listener shows the fade continuing through the freeze.
  • privateAttributes/press.stylex.ts still says a caller's ::before is "replaced" during a press, while FR11 says it merges into the layer and the changeset says the caller's properties win.
  • The cell and cellWithoutLayer JSDoc blocks could keep only the reason, in shorter sentences.
  • ListRowPress.interaction.browser.spec.ts mentions "the 250 ms release"; the release is 200 ms.

[Automated review]

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changes requested: one wording fix. Under a finger, ListItem, Item and TreeList rows now keep fading a release that has started while the page runs script. Mouse and trackpad rows are unchanged, and the component:List FR11 and DEC-6 change has an engineering owner's approval for this head.

Blocking:

  • Miss: the changeset and the description say a row's release keeps fading while the page runs script, but script in the tap's own handlers (pointerup, touchend, or a click that runs before the next frame) still holds the row fully pressed until it ends, as your test a busy first frame after the lift delays the release instead of skipping into it asserts, so please say the fix covers a release that has already started, and start the freeze in phone-check step 2 a frame after the lift.

Worth doing before this moves to main:

  • Newly live check: main now rejects @playwright/test imports in browser specs, so both new specs should import from @astryxdesign/a11y-spec/playwright, and the WebKit case's own chromium.launch() contexts need keepRequestsLocal.
  • privateAttributes/press.stylex.ts still says a caller's ::before is "replaced" during a press, and ListRowPress.interaction.browser.spec.ts mentions a 250 ms release where the release is 200 ms.

[Automated review]

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. Under a finger, while a row is pressed and through its release, the press paints on the row's `::before` and the row is its own stacking context; at rest nothing of it applies, so a row's own `::before` decoration and its children's stacking are as they were. During a press the layer is the row's `::before`, and any `::before` property a caller sets, from `xstyle` (which `Item` composes after the layer) or from a stylesheet, wins over the layer's: a decoration with its own size and background jumps to the row's corner, and the row shows no pressed fill until the release ends. Decoration that should stay through a press belongs on `::after` or a child element. With a mouse or trackpad the row is unchanged: no layer and no new stacking context.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Miss: this holds for script that starts after the release's first frame, not for script in the tap's own handlers. With 700 ms of script in the row's click or touchend, the row stays fully pressed for the whole block, then fades. The PR's own test a busy first frame after the lift delays the release instead of skipping into it asserts that delay, and its WebKit note says WebKit's first frame after such a block is already near rest. Suggested wording: "Once a list row's touch press release has started, it keeps fading while the page runs script, instead of holding and snapping off when the script ends. Script in the tap's own handlers still runs before the release starts, so the row stays pressed until that script ends, then fades."

Comment on lines +27 to +33
import {
chromium,
expect,
test,
type Locator,
type Page,
} from '@playwright/test';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Newly live check: main now rejects @playwright/test imports in browser specs (no-restricted-imports), so this fails lint once the PR moves to main (the same holds for ListRowPressPaths.interaction.browser.spec.ts:19). Import from @astryxdesign/a11y-spec/playwright, which re-exports chromium and expect, and call keepRequestsLocal on the contexts of the WebKit case's own chromium.launch().

Under a finger, an interactive Item, ListItem or TreeList row paints its
press on its own `::before` layer and fades it out as an opacity
animation, so a release that has started keeps fading while the page
runs script. The release keeps the press model's colour, clock and
curve. A fine pointer's row is unchanged.

Co-authored-by: Cindy Zhang <cindyxz@meta.com>
@cixzhang
cixzhang force-pushed the fix/list-row-press-compositor branch from 6128ad5 to e2f2ed7 Compare November 4, 2026 09:01
@cixzhang
cixzhang changed the base branch from fix/press-release-ends-with-its-animation to main November 4, 2026 09:01
@cixzhang

cixzhang commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

/approve-spec e2f2ed7

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Recommended for approval, pending CI: CI has not run on this head yet, because it skips draft pull requests; it runs once the pull request is marked ready for review.

Under a finger, an interactive ListItem, Item or TreeList row now keeps fading a release that has started while the page runs script, and mouse and trackpad rows are unchanged. The component:List FR11 and DEC-6 change has an engineering owner's approval for this head, and the changeset now says that script in the tap's own handlers still holds the row pressed.

Optional cleanups:

  • Miss: the header of privateAttributes/press.stylex.ts lists only cell and cellOnImage, and says only Item and TreeList compose these styles, so cellWithoutLayer and RadioListItem are missing.
  • Miss: cellPressConsts.cellReleaseMs repeats pressConsts.releaseMs, the release clock the controller already reads, so reading that constant would remove the copy.
  • The cell and cellWithoutLayer JSDoc blocks could keep only the reason, in shorter sentences.

[Automated review]

Comment on lines +11 to +18
* @output `rowPressLayerStyles`: the compositor press layer for list rows
* (`cell`) and its own-image arms for a row on the `backgroundImage`
* variant (`cellOnImage`)
* @position Owner module of the private press attribute for the row layer:
* the press controller (utils/pressFeedback.ts) writes the attribute, and
* this file reads it. No package entry exports it. Item and TreeList
* compose these styles after their overlay variant;
* utils/interactionOverlay.cell.test.ts tests them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking (Miss): @output lists cell and cellOnImage but not cellWithoutLayer, and @position says Item and TreeList compose these styles, while RadioListItem composes cellWithoutLayer. Add both so the header matches the module.

*/
export const cellPressConsts = stylex.defineConsts({
/** `timing.press.highlightOutMs`, in ms. */
cellReleaseMs: 200,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking (Miss): this repeats pressConsts.releaseMs (utils/interactionOverlay.stylex.ts:86), the release clock the controller reads as PRESS_FADE_MS. press.stylex.ts already imports pressVars from that module, so CELL_RELEASE could read pressConsts.releaseMs and drop this copy. The tests already pin the two equal, so this is cleanup, not a fix.

@cixzhang
cixzhang marked this pull request as ready for review November 4, 2026 10:33
@cixzhang cixzhang added the ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) label Nov 4, 2026
@cixzhang
cixzhang marked this pull request as draft November 4, 2026 10:33
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · e2f2ed7

Storybook · Sandbox · built from this PR's head

Modified components

Component Size LoC Visual · A11y · RTL
PowerSearch 236.8 kB (+605 B) · 22.2 kB CSS (+377 B) — — · — · —
Pagination 87.2 kB (+540 B) · 16.4 kB CSS (+375 B) — — · — · —
Selector 78.3 kB (+532 B) · 16.1 kB CSS (+375 B) — — · — · —
181 more components
Component Size LoC Visual · A11y · RTL
BreadcrumbItem 68.0 kB (+545 B) · 11.2 kB CSS (+359 B) — — · — · —
TableCell 75.8 kB (+484 B) · 16.3 kB CSS (+394 B) — — · — · —
ContextMenu 74.6 kB (+495 B) · 15.8 kB CSS (+370 B) — — · — · —
TableHeaderCell 75.4 kB (+481 B) · 16.0 kB CSS (+382 B) — — · — · —
Markdown 145.2 kB (+475 B) · 18.7 kB CSS (+380 B) — — · — · —
Table 84.2 kB (+461 B) · 16.5 kB CSS (+392 B) — — · — · —
CheckboxListItem 53.5 kB (+392 B) · 10.4 kB CSS (+384 B) — — · — · —
ContextMenuItem 20.5 kB (+403 B) · 5.9 kB CSS (+369 B) — — · — · —
ListItem 52.8 kB (+379 B) · 10.4 kB CSS (+384 B) — — · — · —
TreeList · Storybook 39.3 kB (+371 B) · 5.3 kB CSS (+387 B) 1,155 (+3) — · — · —
DropdownMenu 73.1 kB (+350 B) · 15.9 kB CSS (+385 B) — — · — · —
MoreMenu 73.3 kB (+348 B) · 15.9 kB CSS (+385 B) — — · — · —
SelectorOption 17.2 kB (+360 B) · 5.8 kB CSS (+371 B) — — · — · —
DropdownMenuSubMenu 57.0 kB (+355 B) · 9.2 kB CSS (+375 B) — — · — · —
DropdownMenuRadioItem 19.0 kB (+357 B) · 6.3 kB CSS (+372 B) — — · — · —
RadioListItem 51.4 kB (+365 B) · 9.4 kB CSS (+362 B) — — · — · —
DropdownMenuCheckboxItem 18.9 kB (+354 B) · 6.3 kB CSS (+372 B) — — · — · —
Item · Storybook 13.9 kB (+354 B) · 5.2 kB CSS (+371 B) 1,600 (+1) — · — · —
DropdownMenuItem 18.2 kB (+349 B) · 5.9 kB CSS (+370 B) — — · — · —
charts/Chart 140.6 kB (+541 B) · 494 B CSS (0) — — · — · —
lab/Comments 258.9 kB (+512 B) · 2.5 kB CSS (0) — — · — · —
lab/CommentThread 253.6 kB (+500 B) · 2.4 kB CSS (0) — — · — · —
richtext/RichTextEditorToolbar 225.4 kB (+499 B) · 454 B CSS (0) — — · — · —
lab/TransferListSelector 83.1 kB (+469 B) · 1.9 kB CSS (0) — — · — · —
lab/TransferList 75.5 kB (+355 B) · 1.7 kB CSS (0) — — · — · —
lab/Schedule 60.2 kB (+7 B) · 3.9 kB CSS (0) — — · — · —
charts/ChartAxis 6.7 kB (0) · 112 B CSS (0) — — · — · —
AlertDialog 83.2 kB (0) · 13.1 kB CSS (0) — — · — · —
AppShell 70.5 kB (0) · 12.2 kB CSS (0) — — · — · —
AspectRatio 3.8 kB (0) · 3.6 kB CSS (0) — — · — · —
Avatar 34.4 kB (0) · 5.4 kB CSS (0) — — · — · —
AvatarGroup 37.1 kB (0) · 5.5 kB CSS (0) — — · — · —
AvatarGroupOverflow 38.6 kB (0) · 5.5 kB CSS (0) — — · — · —
AvatarStatusDot 7.2 kB (0) · 3.6 kB CSS (0) — — · — · —
Badge 4.0 kB (0) · 4.0 kB CSS (0) — — · — · —
Banner 47.0 kB (0) · 9.2 kB CSS (0) — — · — · —
BaseTypeahead 55.1 kB (0) · 9.6 kB CSS (0) — — · — · —
Blockquote 3.6 kB (0) · 3.5 kB CSS (0) — — · — · —
BottomSheetSwitcher 17.1 kB (0) · 3.4 kB CSS (0) — — · — · —
Breadcrumbs 16.0 kB (0) · 3.4 kB CSS (0) — — · — · —
Button 45.0 kB (0) · 8.7 kB CSS (0) — — · — · —
ButtonGroup 9.0 kB (0) · 3.4 kB CSS (0) — — · — · —
Calendar 54.3 kB (0) · 9.3 kB CSS (0) — — · — · —
Card 6.5 kB (0) · 6.4 kB CSS (0) — — · — · —
Carousel 49.4 kB (0) · 9.5 kB CSS (0) — — · — · —
Center 4.2 kB (0) · 4.6 kB CSS (0) — — · — · —
ChatComposer 61.2 kB (0) · 10.6 kB CSS (0) — — · — · —
ChatComposerDrawer 17.5 kB (0) · 4.7 kB CSS (0) — — · — · —
ChatComposerInput 59.2 kB (0) · 10.0 kB CSS (0) — — · — · —
ChatComposerTokenElement 59.2 kB (0) · 10.0 kB CSS (0) — — · — · —
ChatDictationButton 45.6 kB (0) · 8.8 kB CSS (0) — — · — · —
ChatLayout 48.3 kB (0) · 9.3 kB CSS (0) — — · — · —
ChatLayoutScrollButton 45.6 kB (0) · 8.9 kB CSS (0) — — · — · —
ChatMessage 16.7 kB (0) · 3.7 kB CSS (0) — — · — · —
ChatMessageBubble 7.1 kB (0) · 3.7 kB CSS (0) — — · — · —
ChatMessageList 22.8 kB (0) · 6.9 kB CSS (0) — — · — · —
ChatMessageMetadata 19.8 kB (0) · 4.1 kB CSS (0) — — · — · —
ChatSendButton 45.5 kB (0) · 8.7 kB CSS (0) — — · — · —
ChatSystemMessage 6.8 kB (0) · 3.8 kB CSS (0) — — · — · —
ChatTokenizedText 7.1 kB (0) · 4.0 kB CSS (0) — — · — · —
ChatToolCalls 25.9 kB (0) · 8.2 kB CSS (0) — — · — · —
CheckboxInput 48.4 kB (0) · 8.7 kB CSS (0) — — · — · —
CheckboxList 42.8 kB (0) · 7.5 kB CSS (0) — — · — · —
Citation 20.1 kB (0) · 4.4 kB CSS (0) — — · — · —
ClickableCard 14.8 kB (0) · 7.0 kB CSS (0) — — · — · —
Code 3.6 kB (0) · 3.5 kB CSS (0) — — · — · —
CodeBlock 58.9 kB (0) · 10.0 kB CSS (0) — — · — · —
Collapsible 23.3 kB (0) · 4.5 kB CSS (0) — — · — · —
CollapsibleGroup 6.5 kB (0) · 3.3 kB CSS (0) — — · — · —
CommandPalette 51.1 kB (0) · 12.3 kB CSS (0) — — · — · —
CommandPaletteEmpty 6.2 kB (0) · 3.5 kB CSS (0) — — · — · —
CommandPaletteFooter 17.0 kB (0) · 3.7 kB CSS (0) — — · — · —
CommandPaletteGroup 3.5 kB (0) · 3.5 kB CSS (0) — — · — · —
CommandPaletteInput 26.9 kB (0) · 7.4 kB CSS (0) — — · — · —
CommandPaletteItem 10.3 kB (0) · 3.6 kB CSS (0) — — · — · —
CommandPaletteList 16.0 kB (0) · 3.4 kB CSS (0) — — · — · —
ComplexSelector 55.6 kB (0) · 10.4 kB CSS (0) — — · — · —
DateInput 147.2 kB (0) · 15.5 kB CSS (0) — — · — · —
DateRangeInput 71.2 kB (0) · 11.2 kB CSS (0) — — · — · —
DateTimeInput 167.9 kB (0) · 17.2 kB CSS (0) — — · — · —
DialogHeader 47.9 kB (0) · 10.2 kB CSS (0) — — · — · —
Drawer 38.1 kB (0) · 8.2 kB CSS (0) — — · — · —
DrawerHeader 48.0 kB (0) · 11.6 kB CSS (0) — — · — · —
DropdownMenuGroup 6.2 kB (0) · 3.4 kB CSS (0) — — · — · —
DropdownMenuRadioGroup 6.1 kB (0) · 3.4 kB CSS (0) — — · — · —
FileInput 53.6 kB (0) · 9.7 kB CSS (0) — — · — · —
FormLayout 6.4 kB (0) · 3.5 kB CSS (0) — — · — · —
Grid 4.4 kB (0) · 3.8 kB CSS (0) — — · — · —
GridSpan 3.5 kB (0) · 3.4 kB CSS (0) — — · — · —
IconButton 45.1 kB (0) · 8.7 kB CSS (0) — — · — · —
InputGroup 41.5 kB (0) · 7.3 kB CSS (0) — — · — · —
InputGroupText 6.4 kB (0) · 3.9 kB CSS (0) — — · — · —
Kbd 16.7 kB (0) · 3.6 kB CSS (0) — — · — · —
LayerProvider 62.9 kB (0) · 9.4 kB CSS (0) — — · — · —
LayoutPanel 7.6 kB (0) · 4.9 kB CSS (0) — — · — · —
Lightbox 52.2 kB (0) · 9.1 kB CSS (0) — — · — · —
Link 42.6 kB (0) · 6.9 kB CSS (0) — — · — · —
List 5.4 kB (0) · 435 B CSS (0) — — · — · —
MetadataList 16.7 kB (0) · 3.8 kB CSS (0) — — · — · —
MetadataListItem 6.6 kB (0) · 3.5 kB CSS (0) — — · — · —
MobileNav 51.8 kB (0) · 9.3 kB CSS (0) — — · — · —
MobileNavToggle 45.4 kB (0) · 8.7 kB CSS (0) — — · — · —
MultiSelector 73.0 kB (0) · 15.3 kB CSS (0) — — · — · —
NavHeadingMenu 14.8 kB (0) · 3.5 kB CSS (0) — — · — · —
NavIcon 3.5 kB (0) · 3.4 kB CSS (0) — — · — · —
NumberInput 55.0 kB (0) · 10.1 kB CSS (0) — — · — · —
Outline 22.2 kB (0) · 4.2 kB CSS (0) — — · — · —
OverflowList 8.6 kB (0) · 3.7 kB CSS (0) — — · — · —
Overlay 19.8 kB (0) · 4.0 kB CSS (0) — — · — · —
Popover 51.8 kB (0) · 12.8 kB CSS (0) — — · — · —
ProgressBar 10.8 kB (0) · 6.2 kB CSS (0) — — · — · —
RadioList 40.7 kB (0) · 6.4 kB CSS (0) — — · — · —
ResizeHandle 19.5 kB (0) · 4.3 kB CSS (0) — — · — · —
Section 7.6 kB (0) · 6.3 kB CSS (0) — — · — · —
SegmentedControl 81.6 kB (0) · 6.7 kB CSS (0) — — · — · —
SegmentedControlItem 10.6 kB (0) · 4.0 kB CSS (0) — — · — · —
SelectableCard 15.0 kB (0) · 7.2 kB CSS (0) — — · — · —
SideNav 70.9 kB (0) · 10.1 kB CSS (0) — — · — · —
SideNavCollapseButton 45.9 kB (0) · 8.8 kB CSS (0) — — · — · —
SideNavHeading 58.9 kB (0) · 9.9 kB CSS (0) — — · — · —
SideNavItem 54.6 kB (0) · 9.8 kB CSS (0) — — · — · —
SideNavSection 6.6 kB (0) · 3.7 kB CSS (0) — — · — · —
Skeleton 3.8 kB (0) · 3.8 kB CSS (0) — — · — · —
Slider 88.6 kB (0) · 8.3 kB CSS (0) — — · — · —
StatusDot 22.5 kB (0) · 5.2 kB CSS (0) — — · — · —
Step 28.2 kB (0) · 5.6 kB CSS (0) — — · — · —
Stepper 46.7 kB (0) · 8.9 kB CSS (0) — — · — · —
Switch 86.9 kB (0) · 9.5 kB CSS (0) — — · — · —
Tab 11.6 kB (0) · 4.2 kB CSS (0) — — · — · —
TableBody 3.4 kB (0) · 3.3 kB CSS (0) — — · — · —
TableFooter 3.4 kB (0) · 3.3 kB CSS (0) — — · — · —
TableHeader 3.4 kB (0) · 3.3 kB CSS (0) — — · — · —
TableRow 6.5 kB (0) · 4.0 kB CSS (0) — — · — · —
TableSelectionToolbar 54.6 kB (0) · 11.8 kB CSS (0) — — · — · —
TabList 35.4 kB (0) · 6.4 kB CSS (0) — — · — · —
TabMenu 55.6 kB (0) · 9.8 kB CSS (0) — — · — · —
TextArea 46.6 kB (0) · 8.8 kB CSS (0) — — · — · —
TextInput 52.8 kB (0) · 9.9 kB CSS (0) — — · — · —
Thumbnail 49.4 kB (0) · 10.3 kB CSS (0) — — · — · —
TimeInput 131.6 kB (0) · 14.5 kB CSS (0) — — · — · —
Timer 9.5 kB (0) · 5.9 kB CSS (0) — — · — · —
Timestamp 21.3 kB (0) · 8.9 kB CSS (0) — — · — · —
Toast 59.0 kB (0) · 9.0 kB CSS (0) — — · — · —
ToggleButton 46.0 kB (0) · 8.8 kB CSS (0) — — · — · —
ToggleButtonGroup 6.4 kB (0) · 3.4 kB CSS (0) — — · — · —
Token 25.5 kB (0) · 4.8 kB CSS (0) — — · — · —
Tokenizer 69.5 kB (0) · 17.1 kB CSS (0) — — · — · —
Toolbar 34.8 kB (0) · 7.9 kB CSS (0) — — · — · —
TopNav 53.4 kB (0) · 9.6 kB CSS (0) — — · — · —
TopNavHeading 58.4 kB (0) · 9.7 kB CSS (0) — — · — · —
TopNavItem 10.9 kB (0) · 4.0 kB CSS (0) — — · — · —
TopNavMegaMenu 54.0 kB (0) · 10.6 kB CSS (0) — — · — · —
TopNavMegaMenuFeaturedCard 6.9 kB (0) · 3.6 kB CSS (0) — — · — · —
TopNavMegaMenuItem 11.0 kB (0) · 4.2 kB CSS (0) — — · — · —
TopNavMenu 58.2 kB (0) · 9.9 kB CSS (0) — — · — · —
Typeahead 62.9 kB (0) · 11.2 kB CSS (0) — — · — · —
TypeaheadItem 6.3 kB (0) · 3.6 kB CSS (0) — — · — · —
lab/ChartDotGL 7.4 kB (0) · 20 B CSS (0) — — · — · —
lab/ChartDotGLInteractive 8.3 kB (0) · 20 B CSS (0) — — · — · —
lab/ChartHeatmapGL 8.5 kB (0) · 20 B CSS (0) — — · — · —
lab/ChartStreamGL 7.5 kB (0) · 20 B CSS (0) — — · — · —
lab/ChatEmojiPicker 65.8 kB (0) · 773 B CSS (0) — — · — · —
lab/ChatReactionBar 66.3 kB (0) · 1.1 kB CSS (0) — — · — · —
lab/ChatReasoning 7.0 kB (0) · 967 B CSS (0) — — · — · —
lab/ChatTypingIndicator 24.8 kB (0) · 612 B CSS (0) — — · — · —
lab/ChatUnreadDivider 3.6 kB (0) · 348 B CSS (0) — — · — · —
lab/CircularProgress 8.5 kB (0) · 1.1 kB CSS (0) — — · — · —
lab/CodeEditor 14.0 kB (0) · 743 B CSS (0) — — · — · —
lab/CommentComposer 104.5 kB (0) · 1.9 kB CSS (0) — — · — · —
lab/CommentPin 46.8 kB (0) · 1.2 kB CSS (0) — — · — · —
lab/DiffViewer 71.0 kB (0) · 2.1 kB CSS (0) — — · — · —
lab/EmojiPicker 70.4 kB (0) · 722 B CSS (0) — — · — · —
lab/ListInput 82.0 kB (0) · 1.6 kB CSS (0) — — · — · —
lab/LogStream 8.0 kB (0) · 1.6 kB CSS (0) — — · — · —
lab/SelectableCardGroup 50.2 kB (0) · 162 B CSS (0) — — · — · —
lab/Stat 4.2 kB (0) · 646 B CSS (0) — — · — · —
lab/SVGIcon 7.6 kB (0) · 764 B CSS (0) — — · — · —
lab/ThreeDScatterGL 7.6 kB (0) · 20 B CSS (0) — — · — · —
lab/TourStep 57.9 kB (0) · 590 B CSS (0) — — · — · —
richtext/RichTextEditor 278.2 kB (0) · 2.6 kB CSS (0) — — · — · —
richtext/RichTextView 258.4 kB (0) · 2.3 kB CSS (0) — — · — · —

Package size: @astryxdesign/core 6.33 MB packed (+7.4 kB); no new dependencies.

API changes: none in this pull request

Committed API report behind this build: cli 8 rows, theme-butter 1 row, theme-chocolate 1 row, theme-gothic 1 row, theme-matcha 1 row, theme-neutral 1 row, theme-stone 1 row, theme-y2k 1 row; run pnpm api:report.

Wide change (shared styles: packages/core/src/utils/interactionOverlay.stylex.ts, packages/core/src/utils/rowPressLayer.stylex.ts): add ci:run-visual, ci:run-a11y or ci:run-rtl to run them.


Generated by PR Enrichment workflow | View full report

@cixzhang
cixzhang marked this pull request as ready for review November 4, 2026 13:10
@cixzhang
cixzhang merged commit 298bafb into main Nov 4, 2026
119 of 120 checks passed
@github-actions
github-actions Bot deleted the fix/list-row-press-compositor branch November 5, 2026 06:03

This branch was successfully deployed

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

Labels

ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) 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