Repository navigation
feat(core): under a finger a pressable highlights at touch-down, and waits only inside a scroller - #8117
feat(core): under a finger a pressable highlights at touch-down, and waits only inside a scroller#8117vjeux wants to merge 1728 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
456f661 to
99d3b7d
Compare
There was a problem hiding this comment.
Approval is on hold until an engineering owner rules on the draft touch-presentation spec (AST-094) that this pull request adds and builds on, including its proposed amendment of the current user-states pressed-overlay rules (DR1 and DR3, with DR4 also in play). An engineering owner records approval by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Whatever that ruling, these changes are needed:
- A scroll the finger did not drive (a transcript pinned to the bottom,
scrollIntoView, scroll anchoring) cancels the press and suppresses the tap's click inGestureHost's scroll handler, where only the browser's own pan with that finger should. - The cell profile waits 150 ms everywhere, while FR11 limits the delay to scroll views; make the code and the record agree.
- The changeset and docs say the release fades over 250 ms, but at this head the stylesheet still fades over 200 ms.
- The diff carries the whole 28,508-line stack; landing it in its stated order would leave this pull request reviewable as its own commit.
[Automated review]
| } | ||
| } | ||
| if (taken.length > 0) { | ||
| this.arena.browserTookOver(taken, time); |
There was a problem hiding this comment.
Every scroll above a live touch lands here, including one the app drives (a transcript pinned to its end, scrollIntoView, scroll anchoring). browserTookOver sets suppressClick, so the lift cancels touchend and swallows the tap's click. The base only dropped the paint. Consider treating only the browser's own pan with this finger as a takeover, and dropping just the paint for any other scroll.
There was a problem hiding this comment.
Agreed, and confirmed in the code: any ancestor scroll during a live touch went through browserTookOver, which sets suppressClick. The gesture host fix is #8309. A scroll takes the touch only after the finger has travelled the slop; a page-driven scroll under a still finger leaves the press and its click alone. Its test: held button, scrollTop changed by script, lift, and the click fires. This PR picks it up when #8309 lands.
| }, | ||
| /** A table or collection cell: delayed highlight, cancelled by the scroll. */ | ||
| cell: { | ||
| highlightDelay: 'always', |
There was a problem hiding this comment.
'always' makes a row wait 150 ms even where nothing can scroll, but draft FR11 says "The 150 ms delay applies only inside scroll views." Either use 'inScrollers' for cells or change FR11 to say a cell always waits.
There was a problem hiding this comment.
The code is the intended behavior. A UIKit cell always sits in a scroll view, so it always waits the content-touch delay; the UIKit reference measured list cells highlighting about 145 to 150 ms after touch-down. FR11 is being amended to "a cell always waits the delay; a control waits only inside a scroller". That amendment is recorded in the body as proposed, pending owner review.
| '@astryxdesign/core': patch | ||
| --- | ||
|
|
||
| [fix] Under a finger, a button, card, link or other control now highlights in the frame the finger lands, as UIKit's controls do, instead of after a 150 ms delay; inside a scroller it still waits that delay (a flick paints nothing), and a scroll that takes a held press cancels it with no fade and no click. The release fades over UIButton's 250 ms. List rows (Item, TreeList) keep a list cell's press: the 150 ms delay wherever they are, cancelled by 10 px of travel, released on the deselect clock. `usePressFeedback` takes `{profile: 'cell'}` for a local list row; the default is a control. A mouse on a desktop keeps `:active`. |
There was a problem hiding this comment.
At this head the stylesheet's fading animation still runs pressConsts.releaseMs: 200 (utils/interactionOverlay.stylex.ts:116); only the attribute lasts 250 ms. If this merges before the stylesheet follow-up, this note describes a fade users do not get.
PR Analysis Report📚 Storybook Preview🧪 Sandbox PreviewModified ComponentsButton (@astryxdesign/core) · View in Storybook
Item (@astryxdesign/core) · View in Storybook
TreeList (@astryxdesign/core) · View in Storybook
Component Size
Sizes are gzip. Import counts the JavaScript the import loads eagerly; CSS counts the package stylesheet rules referenced by the modules it can load from its own package, lazily loaded ones included.
|
| Component | JavaScript | CSS |
|---|---|---|
| core/BreadcrumbItem | +2.4KB |
+412B |
| core/CheckboxListItem | +2.3KB |
+541B |
| core/ContextMenu | +2.0KB |
+550B |
| core/ContextMenuItem | +2.2KB |
+420B |
| core/DropdownMenu | +2.0KB |
+545B |
| core/DropdownMenuCheckboxItem | +2.3KB |
+404B |
| core/DropdownMenuItem | +2.3KB |
+421B |
| core/DropdownMenuRadioItem | +2.3KB |
+404B |
| core/DropdownMenuSubMenu | +2.3KB |
+408B |
| core/Item | +2.5KB |
+423B |
| core/ListItem | +2.3KB |
+541B |
| core/MoreMenu | +2.0KB |
+545B |
| core/RadioListItem | +2.3KB |
+401B |
| core/SelectorOption | +2.4KB |
+421B |
| core/TreeList | +2.4KB |
+423B |
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.1KB | +555B |
Accessibility Audit
Status: 2 accessibility violation(s) found — 2 serious.
Radio group pattern - 1 issue(s)
- 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
- Rule:
color-contrast· Affects 3/11 stories · Learn more - WCAG: 1.4.3 (Level AA)
- Rule:
RadioList - 1 issue(s)
- 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
- Rule:
color-contrast· Affects 3/14 stories · Learn more - WCAG: 1.4.3 (Level AA)
- Rule:
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
99d3b7d to
eb1d168
Compare
There was a problem hiding this comment.
Approval is on hold until an engineering owner rules on the draft touch-presentation spec (AST-094) that this pull request adds and builds on, including its proposed amendment of the current user-states pressed-overlay rules (DR1 and DR3, with DR4 also in play); an engineering owner records approval by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Whatever that ruling, these changes are needed:
- A scroll the finger did not drive (a transcript pinned to its end,
scrollIntoView, scroll anchoring) cancels a held press and swallows the tap's click inGestureHost's scroll handler, where only the browser's own pan with that finger should. - The cell profile waits 150 ms everywhere, while FR11 limits the delay to scroll views; make the code and the record agree.
- The changeset and docs say the release fades over 250 ms, but the stylesheet still fades over 200 ms.
- The diff carries the whole 28,511-line stack; landing it in its stated order would leave this pull request reviewable as its own commit.
[Automated review]
eb1d168 to
26e487c
Compare
There was a problem hiding this comment.
Approval is on hold until an engineering owner rules on the draft touch-presentation spec (AST-094) that this pull request adds and builds on, and on its proposed amendment of the current user-states pressed-overlay rules (DR1 and DR3), which an owner must approve or update; an engineering owner records approval by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Whatever that ruling, these changes are needed:
- A scroll the finger did not drive (a transcript pinned to its end,
scrollIntoView, scroll anchoring) cancels a held press and swallows the tap's click inGestureHost's scroll handler, where only a pan the finger drives should take it. - The cell profile waits 150 ms everywhere, while FR11 limits the delay to scroll views; make the code and the record agree.
- The changeset, hook docs and story say the release fades over 250 ms, but the stylesheet still fades over 200 ms.
UsePressFeedbackOptionsis not exported from@astryxdesign/core/hooks, unlike the option types of the other hooks.- The diff carries the whole 30,194-line stack; landing it in its stated order would leave this pull request reviewable as its own commit.
[Automated review]
26e487c to
96cd3d7
Compare
|
On the diff size: this PR is stacked on the foundation PRs listed at the top of the body and in its stack line. Only its last commit, 96cd3d7, is this change; the rest of the diff is its predecessors. It narrows as they land in the stated order. |
There was a problem hiding this comment.
Approval waits on an engineering owner's decision: every runtime change rests on draft spec:AST-094, which no owner has approved and whose FR11 would amend the current pressed-state rules in design:user-states (DR1 and DR3). An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner, ideally on a head whose FR11 says what the code does: a list cell always waits 150 ms. Whatever the ruling, these still need changes:
- A scroll the app performs during a held tap cancels the tap's click (
gestures/GestureHost.ts:1214-1230); only a scroll the finger drove should take the touch. gestures/README.mdand theusePressFeedbackdocs still describe a 0.25 s control fade, while the stylesheet fades over 200 ms at this head.- The pull request carries its whole predecessor stack (30,332 authored lines for a 2,509-line commit); land or rebase onto the predecessors so only this commit remains.
[Automated review]
| const taken: number[] = []; | ||
| for (const touch of this.arena.touches) { | ||
| if ( | ||
| touch.takenByBrowser || | ||
| touch.phase === 'ended' || | ||
| touch.phase === 'cancelled' || | ||
| touch.pointerType !== 'touch' || | ||
| this.arena.isClaimed(touch.id) | ||
| ) { | ||
| continue; | ||
| } | ||
| if (touch.target instanceof Node && scroller.contains(touch.target)) { | ||
| taken.push(touch.id); | ||
| } | ||
| } | ||
| if (taken.length > 0) { | ||
| this.arena.browserTookOver(taken, time); |
There was a problem hiding this comment.
Every live, unclaimed touch under any scroll is handed to browserTookOver here, including a scroll the app performs (a pinned transcript, scrollIntoView). GestureArena.ts:342 then sets suppressClick, so the held tap loses its click. The finger-driven test above only arms the brake; apply a finger test here too (the touch travelled past the slop, or the browser sent pointercancel or an uncancelable touchmove).
| }, | ||
| /** A table or collection cell: delayed highlight, cancelled by the scroll. */ | ||
| cell: { | ||
| highlightDelay: 'always', |
There was a problem hiding this comment.
The cell profile waits 150 ms wherever it is, while draft FR11 at this head says "The 150 ms delay applies only inside scroll views." Either the record or this value has to change before the record is approved; the author's reply says the record will.
|
|
||
| - `control` (a button, a switch): the highlight comes on in the same frame | ||
| outside a scroller, the finger is tracked 70 pt beyond the bounds, and | ||
| the release takes UIButton's 0.25 s; |
There was a problem hiding this comment.
At this head the stylesheet fades every profile over 200 ms (utils/interactionOverlay.stylex.ts:116); only the attribute's timer is 250 ms. Describe the 200 ms fade until the fade moves.
96cd3d7 to
c4ffc7a
Compare
…busy first frame outran pressFeedback.ts removes data-astryx-press="fading" on a timer of the release's duration started at the write, for the controller's release and for paintPress's. These fail on that code: with a release animation still running, or still waiting for its first frame, when the clock runs out, the release must stay fading until the animation finishes. Guards: a cancelled animation ends it; no animation ends it on the clock (reduced motion's instant release, a surface without a press layer); a descendant's, an endless or a finished animation is not waited for; a new press keeps its paint when the old release ends later.
…y first frame started it endAfterRelease replaces the clock that removed data-astryx-press="fading" at the release's duration after the write, for the controller's release (apply's fade) and for every other painter's (paintPress). The clock now only looks: at the release's duration, a release animation still running on the surface (one of its own CSS animations, on it or its ::before or ::after, finite and unfinished) is waited for until it finishes or is cancelled, so a busy main thread that starts the animation late ends it late too, at strength 0. With none (reduced motion's instant release, a surface without a press layer, an engine without Web Animations) the release ends on the clock, as before. Nothing at the write reads the document timeline: WebKit starts an animation created after such a read at the time read, which would skip a busy first frame into the release. Any new write, or a new press, cancels the wait; the controller's pending clock is now a cancel function, so the release's wait and every timer clear the same way.
Two quick taps on a button, the second while the first one's release still runs, leave data-astryx-press="on" on the button until the next touch. These fail on that code: pressFeedback.test.ts (a control tapped twice 70 ms apart has no press once its releases end) and ButtonPress.interaction.browser.spec.ts (two touchscreen taps 70 ms apart, both engines: the press goes).
When a touch lands while the last tap's release still runs, the press controller finishes that release first. It was the only press live and the new finger was not recorded yet, so finishing it took the tracking listeners down just before the new touch's press was painted: its lift went unheard and data-astryx-press="on" stayed until the next touch. The listeners are armed again after that finish.
There was a problem hiding this comment.
Incremental review of f67ec83..2a8afd4; prior findings carried: spec_authority, authored_loc, context, user_impact, mechanism, evidence_by_spec_claim, concrete_defects, disposition
Changes are needed before this can be approved: the press profiles follow spec:AST-094 FR11 and the new fix clears a quick second tap as design:user-states DR1 requires, but the diff still carries the whole predecessor stack, 22,632 authored lines for a 2,533-line change, so please land #8082 and #8046 in the stated order and rebase, leaving only the press-profile change to review.
- Before the gesture pull requests land,
GestureHost's scroll handler should take a touch only after 10 px of travel (FR20, as in #8309). mainnow attaches gestures for every pointer (FR19, FR43 andarchitecture:react-component-runtimeINV13), so the host's(pointer: coarse)gate and itsinstanceofchecks on recognizers need to change in those pull requests.
[Automated review]
| const coarseQuery = | ||
| options.isPrimaryPointerCoarse == null && | ||
| typeof view?.matchMedia === 'function' | ||
| ? view.matchMedia('(pointer: coarse)') | ||
| : null; | ||
| this.isPrimaryPointerCoarse = | ||
| options.isPrimaryPointerCoarse ?? (() => coarseQuery?.matches ?? false); | ||
| this.coarseQuery = coarseQuery; |
There was a problem hiding this comment.
main now attaches gestures for every pointer: spec:AST-094 FR19 reads "Recognizers attach for every pointer, with FR43's exceptions", and architecture:react-component-runtime INV13 says "The gesture host attaches for every pointer, with no touch-only query". This (pointer: coarse) read still makes the host register nothing under a fine pointer except an action group's pointerdown (:476-494), so on a desktop a mouse or pen would get no pan or long press once a component adopts the host. The host and its tracker also tell recognizers apart with instanceof (:160, :344, :361), which INV13 now rules out. This belongs in the gesture pull requests before they land.
Main merged meta#8045 and meta#8046. The one conflict is utils/pressFeedback.ts: it takes main's merged meta#8046 text (the finger and button slots, the mouse and key press) and keeps this pull request's own additions, the one writer of the press attribute (writePress, paintPress, readPressPaint) and the header line naming them.
# Conflicts: # internal/interaction-harness/README.md # internal/interaction-harness/src/busy.test.ts # internal/interaction-harness/src/busy.ts # internal/interaction-harness/src/frames.ts # internal/interaction-harness/src/harness.interaction.browser.spec.ts
Main's button slot, merged with meta#8046, ends a press's release at once when the next press starts, by clearing the press attribute directly. On a row another painter holds, that clear erased the hold, which this pull request's one writer refuses. A mouse press on a held row, released, then a press elsewhere must leave the row held. Fails until the early end goes through the one writer.
finish() writes none through writePress, as this pull request's controller paints, so a held row stays its owner's when the next press ends a release early, under the finger and the button slots alike.
Both upstream heads are folded onto main 70ea859 (her meta#8045 frame evidence and meta#8046 coarse press): meta#8446 takes main's harness files, and meta#8073 carries the press controller onto meta#8046's slots. meta#8076 has no change of its own in either set of files, so the merge takes theirs with no hand resolution, and meta#8076 is clean against main again.
# Conflicts: # internal/interaction-harness/README.md # internal/interaction-harness/src/busy.test.ts # internal/interaction-harness/src/busy.ts # internal/interaction-harness/src/frames.ts # internal/interaction-harness/src/harness.interaction.browser.spec.ts # packages/core/src/utils/pressFeedback.ts
meta#8076's new head carries main 70ea859 with meta#8073's writer of the press attribute on main's press controller, and its README names both writers. utils/pressFeedback.ts keeps that controller with this line's own changes on it: the pointer, drag, scroll and visibility listeners through the shared input stream (meta#8356) and touchcancel only while the primary pointer is coarse (meta#8270); the key path stays at the window's bubble phase, so a Space press the page cancels paints nothing. usePressTracker stays in its own module, one tracker for a component's life (meta#8315), so meta#8073's rebuilt-tracker hook and its test are not taken. The harness files are meta#8076's.
…s, and the action-group tracker writes its own The header and writePress called it the one writer of the press attribute, but main's action-group tracker writes its groups' presses itself. They now say what holds, as meta#8076's gestures README does.
…ss release change # Conflicts: # packages/core/src/utils/pressFeedback.ts
…he press attribute The release note said every press painter writes data-astryx-press through one internal writer, but the action-group tracker (hooks/actionGroupTracker.ts) writes its groups' presses itself, as the gestures README and utils/pressFeedback.ts already say. The note now says paintPress writes every press it tracks and the action-group tracker writes its own.
…) into the touch-down highlight Takes meta#8077's press controller as it now stands (main's finger and button slots, meta#8046's mouse-and-Space press, and the release that ends with its animation) and keeps only this PR's behavior on it: under a finger a surface highlights in the frame the finger lands unless a scroller sits above it, where it waits the content-touch delay (spec:AST-094 FR11). That is pressGesture's down.delayed and the controller's isInsideScroller, with their tests. The two press profiles go: they had become identical, meta#8077 moved the row's release to its stylesheet, so Item and TreeList keep meta#8077's pressable props. This PR's listener arming and brake rewrite are not carried either; meta#8077's own listeners and brake stand.
…his branch meta#8082 (a pushed row stays selected until the pop) and meta#8079 (a touch press hides the dividers beside the row) are closed, so their code leaves this branch: the selection hold and its tests, the divider hiding and its tests, their changesets, and their edits to List, Item, TreeListItem, the motion table, the row press layer and the coverage and CSS tests, each back to meta#8077's text.
…4 FR11) One reason per change: what this branch still carried of its earlier scope goes. The listener-on-demand arming and the fling brake are gone from the controller, so their browser case in ListRowPressPaths, the motion table's brake comments and the hook's profile-export test go too; the release-clock guard now reads PRESS_FADE_MS; the story, the browser proofs and the changeset say what remains: under a finger a pressable highlights in the frame the finger lands, and waits the content-touch delay inside a scroller. The double-tap case goes with the arming it guarded; meta#8077's 'two touches in a row' covers a second tap.
… release change # Conflicts: # packages/core/src/utils/pressFeedback.test.ts
…wn highlight The only conflict is two test helpers added side by side (makeScroller here, coarsePrimaryPointer there); both stay.
|
Deployment failed for project astryx with the following error: |
There was a problem hiding this comment.
Changes are needed before this can be approved: the touch-down highlight follows spec:AST-094 FR11 as written, but the diff still carries the whole predecessor stack, 29,580 authored lines for a 682-line change, so please land #8077 (with #8076 and #8360) and rebase, or retarget this pull request onto #8077's branch.
isInsideScrollersteps byparentNode, so a pressable slotted into a shadow-tree scroller lights at touch-down; stepping withparentAcrossShadowfromutils/flatTree.ts, with a test, fixes it.- Before the gesture pull requests land, the host needs to attach for every pointer, as FR19, FR43 and
architecture:react-component-runtimeINV13 now require.
[Automated review]
| node = | ||
| node.parentNode instanceof ShadowRoot | ||
| ? node.parentNode.host | ||
| : node.parentNode; |
There was a problem hiding this comment.
This steps from a slotted element to its light-DOM parent, so a scroller around the slot inside a shadow tree is never visited and the press lights at touch-down where spec:AST-094 FR11 asks for the 150 ms delay. parentAcrossShadow in utils/flatTree.ts already takes the flat-tree step (a slotted node goes to its assignedSlot), as the gesture core's scroll probe and the selection lock do; using it here, with a slotted test case, keeps the two scroller tests in agreement.
…he content-touch delay The browser scrolls the flat tree, so a pressable slotted into a scroller inside a shadow tree is inside that scroller (spec:AST-094 FR11). The controller's scroller check steps by parentNode, from the slotted element to its host, and never sees the shadow scroller: the press lights at touch-down. It must wait the delay.
…able in a shadow scroller waits isInsideScroller now steps with parentAcrossShadow (utils/flatTree.ts), the flat-tree parent the gesture host and the selection lock already use: a slotted node goes to its slot and a shadow root to its host, so a pressable slotted into a scroller inside a shadow tree waits the content-touch delay (spec:AST-094 FR11).
There was a problem hiding this comment.
Changes are needed before this can be approved: the touch-down highlight follows spec:AST-094 FR11 as written, and a pressable slotted into a shadow-tree scroller now waits too, but the diff still carries the whole predecessor stack, 29,603 authored lines for a 705-line change. Please land #8077 (with #8076 and #8360) and rebase, or retarget this pull request onto #8077's branch, so only this change remains.
- Before the gesture pull requests land, the host needs to attach for every pointer, as FR19, FR43 and
architecture:react-component-runtimeINV13 require (gestures/GestureHost.ts:12-23).
[Automated review]
Review this PR alone: vjeux/astryx@uikit/c11-row-press-release...uikit/c01-press-profiles
User need
Part of the UIKit-parity work, which makes Astryx's touch presses feel like an iPhone's. Under a finger, every pressable waits 150 ms before showing the press. In a scrolling list that's right, because the touch may become a scroll. On a button outside any list it reads as lag: iOS highlights a control the moment it's touched.
Problem and solution fit
What led to it: comparing real touches on the iOS 26.2 simulator with Astryx. UIKit delays a touch only inside a scroll view (
delaysContentTouches).spec:AST-094FR11). Rows follow the same rule.Current specification
spec:AST-094FR11;design:user-statesDR4.Public delta and caller burden
No API change;
usePressFeedbackkeeps its signature.Design and alternatives
touch-actionleaves to the browser, stopping at a fixed bar. It lives in the press controller, not the gesture core's scroll probe, because Core's main entry must stay free of the gesture core.Testing
By hand on a phone or the iOS simulator, in the Press feedback stories: a held button in "Buttons" highlights at once; in "Buttons in a scroller" it waits briefly, and a flick scrolls without painting.
What was run: on #8077's controller without this change, 8 unit cases, 2 of the 7 real-touch button cases in Chromium (those outside a scroller) and the finger press accessibility test fail; with it they pass, along with the core unit suite, the button and list-row press specs in Chromium and WebKit, and the press accessibility tests. GitHub Actions isn't running on this repository right now, so these are local runs.
Evidence
Size (gzip): Button JS +427 B, Item +423 B, TreeList +466 B; CSS +0.
Scope
Stack: on #8077, which carries #8076 and #8360. Nothing of the closed #8082 and #8079 remains here.