From 8bb1e4bea9bc925c15a5a6fe06febf1a944baeb3 Mon Sep 17 00:00:00 2001 From: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com> Date: Mon, 14 Sep 2026 17:07:37 +0300 Subject: [PATCH] Clip the native select's value and size the webkit scrollbar on both axes WebKit computes `overflow: visible` on a native select, so a long selected option (the Telegram Command-mode label) painted past the control and became scrollable overflow of `.settings-scroll`, whose `overflow-y: auto` makes `overflow-x` compute to `auto`: Settings > Advanced scrolled sideways in the desktop shell (+147 / +103 / +53 px at 900 / 1000 / 1100 px windows; Blink clips). `select.ui-control` now clips its value. The global `::-webkit-scrollbar` recipe sized only `width` (the vertical bar) since the initial commit, so every horizontal bar kept the 16-17 px UA thickness with the accent thumb; it now sizes both axes and paints a transparent corner. DESIGN.md states that a control never widens its column; the Design System section of DEVELOPMENT.md names the containment rules and the two open gaps (the wizard document does not load style.css; standard scrollbar-* properties opt an element out of the webkit recipe on Blink). Two static pins in tests/test_web_typography_static.py and one WebKit browser test with a synthetic extension form keep the class closed. --- docs/DESIGN.md | 3 + docs/DEVELOPMENT.md | 26 ++- tests/test_ui_settings_overflow_browser.py | 214 +++++++++++++++++++++ tests/test_web_typography_static.py | 60 ++++++ web/style.css | 5 +- web/ui.css | 5 + 6 files changed, 311 insertions(+), 2 deletions(-) create mode 100644 tests/test_ui_settings_overflow_browser.py diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 652cda5ca..e266e2cba 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -155,6 +155,9 @@ never the field's only name. Help and validation belong to that field without changing the alignment of neighboring controls and their actions. Short fixed choices keep native selects, including the platform's own popup. +A control never widens its column: a select shows its chosen label on one +line, clipped at its own edge, and the full label stays in the platform's +popup. Model selection uses the shared editable chooser: suggestions assist typing without becoming an allowlist. A saved unknown model remains editable; a catalog refresh preserves the real input, selection and composition. Escape diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 36bc5455f..849a0da27 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -3169,6 +3169,26 @@ SPA, not a relocatable-page or multi-instance panel framework. `.style.` assignments are review debt (a dynamic measured value may update a narrowly named custom property when that is the real runtime data flow). +- Containment: a control never widens its column, and horizontal overflow + lives in the wrapper that owns the wide content and declares + `overflow-x: auto` (code block, `.md-table-wrap`, tab strip, Costs table + cells) — never in a page scroll body, whose `overflow-y: auto` alone + already makes `overflow-x` compute to `auto`. The shared + `select.ui-control` recipe therefore clips its own value + (`overflow: hidden`): WebKit computes `overflow: visible` on a native + select, so an unclipped option label becomes scrollable overflow of that + page scroller. A grid track holding controls takes a minimum that yields + to its container — `minmax(0, …)`, or + `repeat(auto-fit, minmax(min(100%, Npx), 1fr))`; a fixed px minimum + rescued only by a viewport media query is review debt, because the + viewport does not know how wide the content column is. The global webkit + scrollbar recipe sizes both axes. Enforced by + `tests/test_web_typography_static.py::test_select_control_clips_its_value`, + its `::test_webkit_scrollbar_recipe_covers_both_axes` neighbour, and + `tests/test_ui_settings_overflow_browser.py`; two gaps stay open — the + wizard document loads `ui.css` without `style.css` and keeps native + scrollbars, and an element setting the standard + `scrollbar-width`/`scrollbar-color` opts out of the webkit recipe on Blink. - One semantic button variant expresses one action role: neutral Settings and onboarding controls use the existing `.btn.btn-default`; a one-action result row uses the named `.settings-action-row` contract (status first, @@ -3260,7 +3280,11 @@ stealing usable text space; use the shared responsive component before adding a page-specific layout. A visible change is inspected with vision in at least one relevant real consumer flow. A stored screenshot alone is not verification; mobile or WebKit is not a universal requirement and is -selected from risk. Review-only: scored by CHECKLISTS items 2(i) and 30 +selected from risk. Containment is the WebKit-sensitive exception — a native +select is not clipped there — so a change to a control recipe or a page +scroll body is verified on Playwright WebKit as well as Chromium, measuring +overflow on the scroll body's `scrollWidth` rather than on +`documentElement`. Review-only: scored by CHECKLISTS items 2(i) and 30 (`web_design_system`). ### Browser dialogs diff --git a/tests/test_ui_settings_overflow_browser.py b/tests/test_ui_settings_overflow_browser.py new file mode 100644 index 000000000..4a3c09c49 --- /dev/null +++ b/tests/test_ui_settings_overflow_browser.py @@ -0,0 +1,214 @@ +"""WebKit guard: Settings never grows a horizontal scrollbar. + +The owner's report was a thick red horizontal scrollbar across the bottom of +Settings -> Advanced in the packaged desktop shell (WKWebView). Blink's UA +stylesheet clips a native ``