Skip to content

[bug] Preferences pans sideways on iPhone and the Connections rows crush their own text — the timezone select never shrinks in WebKit, and the guard only runs Chromium #384

Closed

Description

@serge-ivo

Reported

Preferences (global) is broken on mobile — horizontal scroll, and buttons don't wrap so the text
is squashed into one vertical line.

Both confirmed, against production, signed in, at six phone widths in two engines. They are two
separate defects, and a third one is why the first has been reported and closed as
"not reproducible" twice (#333).

1. The horizontal scroll is real, and it exists ONLY in WebKit

main.scrollWidth - main.clientWidth on /console/preferences:

viewport Chromium WebKit
320 0 59
360 0 27
375 0 18
390 0 9
414 0 0
430 0 0

Culprit: the Timezone row — store/console/src/pages/Preferences.tsx:192-207.

<div className="flex justify-between items-center py-2.5 text-sm gap-3">
  <span className="text-muted font-medium">Your timezone</span>
  <select … className="text-sm bg-paper border border-line rounded-lg px-3 py-2 max-w-[60%]">

Measured on that exact element (scrollWidth - clientWidth): +76px at 320, +34px at 390, which
propagates up through the card and the Page container until the whole page pans.

The <select> is a flex item with min-width: auto, so its automatic minimum size is its content
width — and its content is the longest option (America/Argentina/Buenos_Aires,
Not set — agents will say UTC). Chromium clamps that; WebKit does not, so max-w-[60%] caps
the used width and the minimum still wins. There is no min-w-0 on it, and nothing truncates.

Every phone that matters runs WebKit.

2. The squashed text is the Connections rows

store/console/src/components/AccountConnections.tsx:163 (and :132 for the GitHub row):

<div className="flex items-start justify-between gap-3 mb-3">
  <div className="text-sm min-w-0 [overflow-wrap:anywhere]"> … Gmail · connected as … </div>
  <div className="flex gap-2 shrink-0">   ← Reconnect + Disconnect, ~185px, will not wrap

At 320px the button pair takes the row and the text column is left with 67px, and runs 119px tall —
measured, both engines. The email renders as:

connecte
d as
serge.pro.
job@gmail
.com

Note the two halves of this are individually correct and were added on purpose:
[overflow-wrap:anywhere] (from #333) is what stops the unbreakable email token overflowing the
page, and shrink-0 is what stops the buttons collapsing. Together they mean the row cannot
overflow and cannot wrap, so all of the pressure lands on the one element that will yield — the
text. The row needs flex-wrap so the buttons drop below the label at narrow widths; that is what
the chip rows in VoiceFields.tsx:202,221 already do.

At 390px it is not catastrophic but still wrong: the Gmail label wraps to three lines beside two
buttons.

3. Why this was closed as "not reproducible" twice — the guard is Chromium-only

playwright.config.ts:26-31 declares exactly one project:

projects: [ { name: "chromium", use: { ...devices["Desktop Chrome"] } } ],

So measureOverflow, the wide geometry walk and the docOv tripwire — all of the machinery #333
built and hardened specifically for "the page scrolls sideways on mobile" — run in the one engine
where this bug does not exist. Two rounds of investigation on #333 correctly found nothing on
Preferences, because in Chromium there is nothing to find.

The measurement is otherwise sound; it just has no WebKit to run in. Adding a mobile-WebKit project
is what turns this class of defect from "reported by the owner on a phone" into "caught in CI".

Suggested fix

  1. Timezone select — min-w-0 on the select (the automatic minimum size is the actual cause),
    and let it take the full row width on a phone (w-full sm:max-w-[60%]) rather than sitting beside
    the label. A <select> in a flex row needs min-w-0 as a rule; this is the first one measured,
    probably not the only one.
  2. Connections rows — flex-wrap on the two rows in AccountConnections.tsx (:132, :163), so
    the button group drops under the label instead of crushing it.
  3. Guard — add a WebKit mobile project to playwright.config.ts and run the existing overflow
    guards under it. Without this, fix (1) has no regression test in the engine that needs it.

Method

Playwright against https://proagentstore.online/console/preferences, real session, viewports
320/360/375/390/414/430, chromium 141 and WebKit 26.4. Measured documentElement and main
overflow plus every element's right edge and own scrollWidth. Screenshots at 320 and 390 confirm
both symptoms visually.

Supersedes the Preferences half of #333 (the Profile half of that ticket is fixed and verified).

Files: store/console/src/pages/Preferences.tsx:192-207,
store/console/src/components/AccountConnections.tsx:132,163, playwright.config.ts:26-31,
e2e/console.spec.ts (measureOverflow).

Activity

  1. serge-ivo commented on Aug 7, 2026

    @serge-ivo
    ContributorAuthor

    Self-review: fix (3) will probably turn CI red across routes this ticket never measured — scope it deliberately

    I recommended adding a WebKit mobile project to playwright.config.ts so the overflow guards run in the engine where the bug exists. That is right, and it is also the riskiest item here: I only measured /console/preferences. The console has a dozen top-level routes and 14 instance surfaces, none of them ever measured in WebKit, and the cause found here (<select> min-width:auto resolving to the widest option, which Chromium clamps and WebKit does not) is a generic pattern, not a one-off. Every <select> in a flex row is a candidate.

    So adding the project as a blanket CI gate could fail a batch of specs on the first run and block merges on work unrelated to whoever adds it. Two saner shapes:

    • add the WebKit project but scope it to the mobile-overflow spec only to start, and widen once the tree is clean; or
    • add it non-blocking (continue-on-error / a separate reporting job) for one cycle, triage what it finds, then make it required.

    Either way the finding it produces is a feature, not a problem — it is the same class of bug the owner keeps reporting from a phone. It just should not arrive as a surprise red main.

    Two smaller notes on my own measurement:

    • The row's own overflow (+76px at 320, +34px at 390) is larger than the page's (+59 / +9), because ancestors absorb part of it. The page number is the one a user feels; the row number is the one to fix. Neither is wrong, but do not treat them as the same quantity.
    • I did not verify the timezone select is the only WebKit offender on this page — it is the only one the walk found at 320 and 390, which is not the same claim as "there are no others at other widths or in other states (a long msg string, a connected Drive row, an error line)".

    Fixes (1) and (2) — min-w-0 + full-width-on-phone for the select, flex-wrap on the two Connections rows — are safe, local, and carry no behavioural risk.

  2. serge-ivo commented on Aug 7, 2026

    @serge-ivo
    ContributorAuthor

    The guard's other hole: it sweeps the right routes with content that cannot reproduce the bug

    Found while fixing the same class of defect on Notifications (horizontal scroll on mobile, reported by the owner today).

    The cause there was identical in kind: a row's text column is a flex item with default min-width:auto, so it cannot shrink below its longest unbreakable token — and a notification body carries paths (pas/platform: Here are the 10 largest files in …). Measured +110px at 320, +40px at 390, and unlike the timezone select this one overflows in Chromium too.

    So why is CI green? /console/notifications is in the guard's route list (console.spec.ts:1938). The answer is at console.spec.ts:163:

    if (path === "/v1/notifications") return json({ notifications: [], unreadCount: 0 });

    The guard visits the page and measures an empty list. It has never seen a notification row. The route appears covered and the coverage is nominal.

    That generalises past this one fixture. Both defects found this week are min-content overflow, whose trigger is the longest string on the page — so a guard fed short, tidy fixture data is structurally unable to catch the entire class, in any engine. The WebKit project (fix 3) is necessary and would still not have caught Notifications.

    Suggested addition to fix (3): give the fixtures a hostile string. Every mock that feeds user-visible text — notification bodies, agent names, instance names, file names, error messages, connector account labels — should include at least one row carrying a long unbroken token (a repo path, a URL, a 40-char email). That is one edit per fixture and it turns the existing guards, which are otherwise sound, into something that can actually fail.

    The Notifications fix itself (min-w-0 + [overflow-wrap:anywhere], matching AccountConnections.tsx) is verified in the live DOM in both engines: 110 → 0 at 320px, 40 → 0 at 390px.

  3. serge-ivo commented on Aug 8, 2026

    @serge-ivo
    ContributorAuthor

    Fixed in 79bf551. CI is green — run on that commit passed, which is the verification that matters here, and main is clean for the first time in 13 commits.

    Root cause, and why the previous fix looked right

    WebKit's menulist renderer silently overrides overflow on a &lt;select&gt; — computed overflow-x reads visible with the rule live and matching. The control's whole option list, sized by the widest option rather than the selected label, therefore stays in every ancestor's layout overflow. contain: paint is the declaration WebKit cannot decline, and that is the fix.

    8dd92f1 was measured honestly and was still wrong — on macOS WebKit, where the labels happen to fit. CI runs Linux WebKit, where the page never stopped panning. macOS WebKit is green with or without the change, which is exactly why two rounds of measurement on #333 and the fix here all reported clean. Reproduced in mcr.microsoft.com/playwright:v1.60.0-noble, failing the same 10 specs as CI run 31233309026.

    This corrects the guidance in this repo: "check it in WebKit" is not sufficient on a Mac. The engine that matters is Linux WebKit.

    Measurements (Linux WebKit, main.scrollWidth − main.clientWidth)

    viewport before after
    320 68 0
    360 28 0
    375 13 0
    320 @ 1.3× 182 0
    390 @ 1.3× 112 0

    Rejected after measuring, all still 68px at 320: overflow: clip, overflow: hidden !important, field-sizing: content, min-width: 0. appearance: none does reach 0 and was rejected — it works by making the control stop being a native menulist, which deletes the disclosure arrow from ~40 selects.

    Two diagnoses in the earlier tickets were backwards, and are corrected in the code: it is the widest option, not the selected label (voice-tts-provider widened its card by 43px while displaying a value that fits), and it was never only the timezone row — the 68px came from two cards, the widest option on the page belonging to a Voice select.

    The new guard asserts the mechanism, not the symptom

    no select widens its own row at 320/390px. The failure signature — mainOv=68 with wide empty — is the one class wide structurally cannot name, because the escaping content belongs to a native control with no element to measure. Attribution works by taking each control out of flow and re-measuring. Against the reverted stylesheet it names all four real offenders, and it is strictly stronger than the page check: it catches 11px at 390px that mainOv reports as 0.

    Verified rather than assumed: box-shadow is not clipped by paint containment, so select:focus's ring is unchanged. #390's type-scale floor and #389's touch-target floor are untouched and green.

    Left for a separate ticket: store/admin/src/index.css carries the same select rule and the same latent defect, but has no mobile geometry guard and no measurement — outside this change's blast radius.

  4. added 2 commits that reference this issue on Aug 8, 2026
    52741d0
    28a3097
  5. added a commit that references this issue on Oct 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfrontendFrontend / UI work

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions