cmux-next: Settings scrolls in one place (cx-jh7kq)

Merged
TE
25d ago
+377-53

Summary

Settings now scrolls in one place. Before, an open select pop-up made the document itself scrollable. That produced a second scrollbar, a column that slid up under the window with dead space below, rows cut mid-row at the inner edge, and pop-ups drawn past the scroll area.

Cause. Select and popover pop-ups were portaled to <body> with Base UI's default absolute positioning, and .ui-menu had no height cap. While the content column scrolled, an open pop-up followed its anchor out of the viewport and grew the document: in a 700 px window the document reached 1232 px. From then on the wheel chained from the content column to the document, because nothing clipped html/body and .content had no overscroll containment. Opening the font or theme list also pushed every row below it down, since it was an inline panel.

Fix.

  • One scroller. html/body and .settings clip their overflow, so the document never scrolls. .content (and the sidebar) contain their overscroll.
  • Soft edges. .content fades rows out over its top 18 px and bottom 28 px instead of a hard clip. The fades sit inside the padding, so nothing is faded at rest, and the scrollbar strip on the right stays unmasked.
  • Pop-ups. UiProvider gains overlayPosition, and Settings opts in to "fixed"; Menu and Popover pass it to their positioners. A fixed pop-up follows its row without growing the document. Every other page keeps absolute.
  • Shared ui.css. A select's options are capped to the room on their side (--available-height, at most 420 px) and scroll inside. A positioner whose anchor is scrolled out of view (data-anchor-hidden) is hidden until the anchor is back.
  • Font and theme list. DomainListEditor's list is now a Popover over the rows rather than an in-flow panel. Its own Escape handler went away, so its ui-rules pending entry is deleted.

There was never virtualization: every row already renders once. The new test pins that, so a later change can't add remounts.

Testing

Boundary test: webviews/test/settings-scroll.test.ts, on the browser lane. It runs in real Chromium and WebKit against the settings dev server with the mock (?mock), in a 1000×700 window, and checks:

  1. Wheeling Appearance (97 rows) from top to bottom and past the end leaves exactly one row scroller (.content), the document never scrolls, and no row is removed, replaced or moved.
  2. A select opened near the bottom edge (sidebar.sectionLook) while the content is wheeled down and up: the document never grows, the shown pop-up stays on screen within 12 px of its anchor, and it may be hidden only while its anchor is out of view.
  3. Opening the font list moves no row.
  4. .content has the edge fade.
  • Red on base (commit 1c57ebcddba6, Chromium): cases 2–4 fail. Case 2: document 832px … 1032px … 1232px, pop-up off screen. Case 3: 98 rows moved. Case 4: mask none. Case 1 passes on base, because nothing virtualizes.
  • Green on this head (Chromium): 4/4. WebKit cannot launch on the machine that ran it, so CI's react-apps-check runs both engines.
  • Also green locally:
    • the 27 settings and src/ui unit test files;
    • test/ui-rules.test.ts;
    • the ui-a11y and composer-menu-width browser suites;
    • gallery-coverage and gallery-play;
    • bun run check.

Before/after: the new gallery variant pages.settings › play-select-scroll opens that select near the bottom and scrolls the content under it. The PR gallery renders base vs head. The font-picker variant shows the list as a pop-up.

Changelog

  • Fixed: Settings scrolls in one place: no second scrollbar, rows fade at the edges, and open pop-ups stay with their row.

closes cx-jh7kq

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Fixes the Settings page so it scrolls in a single place instead of letting an open select pop-up make the document scroll. That previously caused a second scrollbar, dead space below the column, rows cut mid-row, and pop-ups drawn past the scroll area.

  • The document never scrolls: the content column is the only scroller, with contained overscroll and edges that fade rows out instead of clipping them.
  • Select and popover pop-ups now render fixed and follow their row without growing the document; a long list's options scroll inside it, and a pop-up waits hidden while its anchor is scrolled out.
  • The font and theme list is a pop-over over the rows rather than an in-flow panel that pushed rows down; clicking its button again closes it, close clears the filter, and a row disabled while open closes it too.
  • The document clip applies only to the Settings page, so gallery frames that borrow its stylesheet keep their own scrolling.