Skip to content

Harden CustomSelect against a stale open-time scroll dismissing the menu - #1490

Merged
David Pine (IEvangelist) merged 2 commits into
mainfrom
dapine/harden-customselect-scroll
Aug 14, 2026
Merged

Harden CustomSelect against a stale open-time scroll dismissing the menu#1490
David Pine (IEvangelist) merged 2 commits into
mainfrom
dapine/harden-customselect-scroll

Conversation

@IEvangelist

Copy link
Copy Markdown
Member

Summary

Hardens the footer language CustomSelect against a stale, open-time scroll event that could dismiss the menu the same frame it opened. This was surfacing as a flaky E2E test on the mobile-chromium project and intermittently red-lighting otherwise-unrelated chore PRs (e.g. #1487, run 31721890999).

Root cause

CustomSelect.astro registers a capture-phase window scroll listener that dismisses the menu on any page scroll, so the position: fixed listbox never visually detaches from its trigger. That is the desired behavior for a real scroll — but on small touch viewports, Playwright's .click() first has to scroll the far-below-the-fold footer control into view. On the Pixel 7 viewport that scroll event can be delivered a frame after open() runs, even though the page has already settled, so the freshly opened menu was immediately closed. The language selector stays open while its listbox is scrolled test then failed waiting for the listbox to be visible. Desktop (1440×900) and tablet (iPad Pro 11) scroll far less on the way to the footer and never hit the race.

Fix

  • Capture window.scrollX/scrollY when the menu opens.
  • In the scroll handler, ignore events that report that same already-settled offset (with 2px of slop for sub-pixel rounding on high-DPI mobile) — only dismiss once the page actually moves underneath the menu.
  • Split the combined viewport handler so resize still closes unconditionally while scroll takes the guarded path. The existing "scroll originates inside the listbox" guard is preserved.

The change is deterministic rather than timing-based (no added waits/retries), so it hardens the behavior for real users too, not just the test.

Testing

  • astro sync clean (no type errors in the client script).
  • pnpm test:unit:components — 62/62 pass.
  • Added a deterministic regression assertion to ui-regressions.spec.ts that dispatches a same-offset window scroll after opening and asserts the menu stays open. This fails on the old handler and passes with the fix, on every project. Full E2E runs in CI.

The footer CustomSelect dismisses its menu on any page scroll so the fixed
listbox never detaches from its trigger. On small touch viewports, tapping the
footer control first scrolls it into view; that scroll event can be delivered a
frame after open() runs, even though the page already settled, so the freshly
opened menu was immediately dismissed. This made the "language selector stays
open while its listbox is scrolled" E2E flake on the mobile-chromium project
(desktop/tablet scroll far less and never hit it).

Capture the window scroll offset when the menu opens and ignore scroll events
that report that same already-settled offset; only dismiss once the page
actually moves underneath the menu. Existing behavior is preserved (scrolling
inside the listbox keeps it open, a real page scroll and resize still close it),
and a deterministic regression assertion covers the stale same-offset scroll.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 13, 2026 19:04

Copilot AI 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.

Pull request overview

This PR hardens the footer CustomSelect behavior so the language selector menu isn’t immediately dismissed by a stale, open-time scroll event (a race that was causing flaky mobile Playwright E2E failures). The fix distinguishes between “real” page scrolling and same-offset scroll events delivered after opening.

Changes:

  • Capture window.scrollX/scrollY at menu open time and ignore subsequent scroll events that report the same offset (within a small tolerance).
  • Split the previous combined viewport-change handler into dedicated resize (always close) and guarded scroll (conditionally close) handlers.
  • Add an E2E regression assertion to ensure a same-offset scroll event does not close the open language selector.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/frontend/src/components/CustomSelect.astro Captures open-time scroll offsets and guards the window scroll handler to avoid immediate stale-scroll dismissal while preserving close-on-real-scroll behavior.
src/frontend/tests/e2e/ui-regressions.spec.ts Adds a deterministic regression assertion to confirm the menu stays open when a same-offset window scroll event fires after opening.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/frontend/src/components/CustomSelect.astro
@aspire-repo-bot

Copy link
Copy Markdown
Contributor

Frontend HTML artifact ready

The latest frontend build uploaded the frontend-dist artifact for PR #1490. Use the VS Code button below to open this PR with GitHub Artifacts Explorer and browse the built HTML locally.

VS Code: Open PR #1490 artifacts

This comment updates automatically when a new frontend build artifact is uploaded.

Comment thread src/frontend/src/components/CustomSelect.astro

@adamint Adam Ratzman (adamint) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The stale window-scroll path works in real Chromium across desktop, mobile, and tablet projects, with 21 focused scenarios passing. I left one must-fix inline: ancestor element scrolls now keep the fixed listbox open and detached because window.scrollX/Y do not move. That looks fixable in place, so approving with the comment.

Address PR review feedback:

- The open-time same-offset guard now only applies to page/document/window scrolls, whose position window.scrollX/Y tracks. An ancestor element scroll (e.g. a modal body) leaves window.scrollX/Y unchanged but still slides the trigger out from under the fixed listbox, so those now always close the menu instead of leaving it detached.

- Extract the 2px offset tolerance into a named scrollSettleTolerancePx constant.

- Add a deterministic E2E regression asserting an ancestor-element scroll dismisses the open language selector.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@IEvangelist
David Pine (IEvangelist) enabled auto-merge (squash) August 14, 2026 15:27
@IEvangelist
David Pine (IEvangelist) merged commit bcab853 into main Aug 14, 2026
10 checks passed
@IEvangelist
David Pine (IEvangelist) deleted the dapine/harden-customselect-scroll branch August 14, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants