From 5b388cdbb471b99109551ec4dacf30722ceb797b Mon Sep 17 00:00:00 2001 From: dfliess Date: Mon, 13 Jul 2026 18:07:54 +0200 Subject: [PATCH 1/9] test(web-admin): add a mobile overflow smoke suite Playwright project "mobile" (emulated iPhone 13) asserting the audited routes render without horizontal overflow. Inert until the viewport meta serves the real device width. --- web-admin/playwright.config.ts | 12 +++ web-admin/tests/mobile-smoke.spec.ts | 121 +++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 web-admin/tests/mobile-smoke.spec.ts diff --git a/web-admin/playwright.config.ts b/web-admin/playwright.config.ts index 6b6a7b10b110..6e9e3b36b2a7 100644 --- a/web-admin/playwright.config.ts +++ b/web-admin/playwright.config.ts @@ -41,6 +41,18 @@ const config: PlaywrightTestConfig = { storageState: ADMIN_STORAGE_STATE, }, }, + { + // Responsive smoke checks run on an emulated phone against the same + // seeded stack. Limited to mobile-smoke so the desktop suites aren't + // re-run here. + name: "mobile", + dependencies: process.env.E2E_NO_SETUP_OR_TEARDOWN ? [] : ["setup"], + testMatch: "mobile-smoke.spec.ts", + use: { + ...devices["iPhone 13"], + storageState: ADMIN_STORAGE_STATE, + }, + }, ], }; diff --git a/web-admin/tests/mobile-smoke.spec.ts b/web-admin/tests/mobile-smoke.spec.ts new file mode 100644 index 000000000000..c3f1724482cb --- /dev/null +++ b/web-admin/tests/mobile-smoke.spec.ts @@ -0,0 +1,121 @@ +import { expect, type Page } from "@playwright/test"; +import { test } from "./setup/base"; + +/** + * Mobile smoke manifest. + * + * web-admin serves the real device viewport on every route (see `app.html`). + * The route ids listed here are the surfaces that were explicitly audited for + * narrow screens and are exercised below at a phone viewport. When a new + * surface is made responsive, add its route id here and a matching smoke URL + * to {@link TEST_ROUTES}; the coverage guard fails otherwise. + * + * `/[organization]/[project]/-/ai/[conversationId]` is intentionally not + * listed yet: its layout is covered by the `-/ai` surface, and the smoke test + * has no seeded conversation to visit. + */ +const MOBILE_READY_ROUTES: string[] = [ + "/[organization]/[project]", + "/[organization]/[project]/-/ai", + "/[organization]/[project]/explore/[dashboard]", + "/[organization]/[project]/canvas/[dashboard]", +]; + +/** + * Maps each mobile-ready SvelteKit route id to a concrete URL in the e2e + * environment (org `e2e`, projects `openrtb` / `adbids` seeded by `setup`). + */ +const TEST_ROUTES: Record = { + "/[organization]/[project]": "/e2e/openrtb", + "/[organization]/[project]/-/ai": "/e2e/openrtb/-/ai", + "/[organization]/[project]/explore/[dashboard]": + "/e2e/openrtb/explore/auction_explore", + "/[organization]/[project]/canvas/[dashboard]": + "/e2e/openrtb/canvas/bids_canvas", +}; + +/** + * Asserts that the page produces no horizontal overflow at the current + * viewport width: neither the document scrolls horizontally, nor does any + * visible element extend past the right edge of the viewport. Offending + * element selectors are included in the failure message. + */ +async function assertNoHorizontalOverflow(page: Page) { + const result = await page.evaluate(() => { + const viewportWidth = window.innerWidth; + const doc = document.scrollingElement; + const documentScrolls = doc ? doc.scrollWidth > doc.clientWidth + 1 : false; + + const describe = (el: Element): string => { + const tag = el.tagName.toLowerCase(); + const id = el.id ? `#${el.id}` : ""; + const cls = + typeof el.className === "string" && el.className.trim() + ? `.${el.className.trim().split(/\s+/).join(".")}` + : ""; + return `${tag}${id}${cls}`; + }; + + // Collect visible elements whose right edge extends past the viewport. + // Cap the list so a systemic failure does not produce an unwieldy message. + const offenders: string[] = []; + for (const el of Array.from(document.querySelectorAll("*"))) { + const rect = el.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) continue; + if (rect.right > viewportWidth + 1) { + offenders.push(`${describe(el)} (right=${Math.round(rect.right)})`); + if (offenders.length >= 10) break; + } + } + + return { + viewportWidth, + scrollWidth: doc?.scrollWidth ?? null, + clientWidth: doc?.clientWidth ?? null, + documentScrolls, + offenders, + }; + }); + + expect( + result.documentScrolls, + `Document scrolls horizontally at ${result.viewportWidth}px ` + + `(scrollWidth=${result.scrollWidth}, clientWidth=${result.clientWidth}).`, + ).toBe(false); + + expect( + result.offenders, + `Elements overflow the ${result.viewportWidth}px viewport:\n` + + result.offenders.map((o) => ` - ${o}`).join("\n"), + ).toEqual([]); +} + +test.describe("Mobile smoke", () => { + // Coverage guard: every mobile-ready route must have a smoke URL so it is + // actually exercised here. Fails loudly when a route is added to + // MOBILE_READY_ROUTES without a corresponding TEST_ROUTES entry. + test("every mobile-ready route has a smoke URL", () => { + const missing = MOBILE_READY_ROUTES.filter( + (routeId) => !(routeId in TEST_ROUTES), + ); + expect( + missing, + `Routes in MOBILE_READY_ROUTES missing a TEST_ROUTES entry: ` + + missing.join(", "), + ).toEqual([]); + }); + + if (MOBILE_READY_ROUTES.length === 0) { + // Keep the suite green (and explicit) until the first surface is migrated. + test.skip("no mobile-ready routes yet", () => {}); + } + + for (const routeId of MOBILE_READY_ROUTES) { + test(`no horizontal overflow: ${routeId}`, async ({ page }) => { + const url = TEST_ROUTES[routeId]; + test.skip(!url, `No TEST_ROUTES entry for ${routeId}`); + await page.goto(url); + await assertNoHorizontalOverflow(page); + }); + } +}); From 3961cb1cf538a2c24dd93d2fe700a62d5cb96986 Mon Sep 17 00:00:00 2001 From: dfliess Date: Mon, 13 Jul 2026 18:08:07 +0200 Subject: [PATCH 2/9] fix(web-admin): cap fixed widths and fit admin tables on narrow viewports Mechanical pass: floating and fixed-width elements get min(Npx, calc(100vw - 2rem)) or max-w caps, and the org/admin tables scroll horizontally inside their own container instead of overflowing the page. --- .../AccessRequestContainer.svelte | 2 +- .../src/features/alerts/CreateAlert.svelte | 2 +- .../src/features/alerts/EditAlert.svelte | 2 +- .../contact/ChangeBillingContactDialog.svelte | 2 +- .../dialog/WelcomeToRillCloudDialog.svelte | 4 ++- .../src/features/bookmarks/Bookmarks.svelte | 2 +- .../features/branches/BranchesSection.svelte | 4 ++- .../settings/UploadImagePopover.svelte | 2 +- .../dialogs/ConvertGuestToMemberDialog.svelte | 2 +- .../table/groups/OrgGroupsTable.svelte | 22 +++++++++------ .../table/users/OrgUsersTable.svelte | 28 +++++++++++-------- .../status/overview/ProjectClone.svelte | 6 +++- .../settings/SettingsItemContainer.svelte | 2 +- .../src/routes/-/auth/device/+page.svelte | 2 +- .../src/routes/-/welcome/theme/+page.svelte | 6 +++- .../-/create-project/+page.svelte | 6 ++-- .../[organization]/-/users/+page.svelte | 2 +- .../-/users/groups/+page.svelte | 2 +- .../-/users/guests/+page.svelte | 4 ++- .../[project]/-/invite/+page.svelte | 4 ++- .../[project]/-/reports/[report]/+page.svelte | 2 +- 21 files changed, 67 insertions(+), 41 deletions(-) diff --git a/web-admin/src/features/access-request/AccessRequestContainer.svelte b/web-admin/src/features/access-request/AccessRequestContainer.svelte index f04a34f4ff38..4ec460cbc88a 100644 --- a/web-admin/src/features/access-request/AccessRequestContainer.svelte +++ b/web-admin/src/features/access-request/AccessRequestContainer.svelte @@ -1,6 +1,6 @@
{/snippet} - + {m.billing_change_billing_contact()} diff --git a/web-admin/src/features/billing/plans/dialog/WelcomeToRillCloudDialog.svelte b/web-admin/src/features/billing/plans/dialog/WelcomeToRillCloudDialog.svelte index d9387e943d71..1f3554727d90 100644 --- a/web-admin/src/features/billing/plans/dialog/WelcomeToRillCloudDialog.svelte +++ b/web-admin/src/features/billing/plans/dialog/WelcomeToRillCloudDialog.svelte @@ -31,7 +31,9 @@ {/snippet} - +
diff --git a/web-admin/src/features/bookmarks/Bookmarks.svelte b/web-admin/src/features/bookmarks/Bookmarks.svelte index bd4627044b2d..0a83b117bb10 100644 --- a/web-admin/src/features/bookmarks/Bookmarks.svelte +++ b/web-admin/src/features/bookmarks/Bookmarks.svelte @@ -183,7 +183,7 @@ {/snippet} - + (showDialog = true)}>
diff --git a/web-admin/src/features/branches/BranchesSection.svelte b/web-admin/src/features/branches/BranchesSection.svelte index 093cf637b1a3..14602e092d76 100644 --- a/web-admin/src/features/branches/BranchesSection.svelte +++ b/web-admin/src/features/branches/BranchesSection.svelte @@ -349,7 +349,9 @@ {m.branch_read_only()} -
+
{m.branch_not_editable()} {m.branch_recreate_with()} diff --git a/web-admin/src/features/organizations/settings/UploadImagePopover.svelte b/web-admin/src/features/organizations/settings/UploadImagePopover.svelte index f57ddaaf7061..670a64b8f553 100644 --- a/web-admin/src/features/organizations/settings/UploadImagePopover.svelte +++ b/web-admin/src/features/organizations/settings/UploadImagePopover.svelte @@ -101,7 +101,7 @@
{m.settings_upload_org_image_title({ label })} diff --git a/web-admin/src/features/organizations/user-management/dialogs/ConvertGuestToMemberDialog.svelte b/web-admin/src/features/organizations/user-management/dialogs/ConvertGuestToMemberDialog.svelte index 3b77c2beaa36..04933fa188b5 100644 --- a/web-admin/src/features/organizations/user-management/dialogs/ConvertGuestToMemberDialog.svelte +++ b/web-admin/src/features/organizations/user-management/dialogs/ConvertGuestToMemberDialog.svelte @@ -75,7 +75,7 @@ {/snippet} { e.preventDefault(); open = false; diff --git a/web-admin/src/features/organizations/user-management/table/groups/OrgGroupsTable.svelte b/web-admin/src/features/organizations/user-management/table/groups/OrgGroupsTable.svelte index 5b5256bc1ce7..dc470f815f97 100644 --- a/web-admin/src/features/organizations/user-management/table/groups/OrgGroupsTable.svelte +++ b/web-admin/src/features/organizations/user-management/table/groups/OrgGroupsTable.svelte @@ -70,12 +70,16 @@ $: dynamicTableMaxHeight = data.length > 12 ? `calc(100dvh - 300px)` : "auto"; - + +
+ +
diff --git a/web-admin/src/features/organizations/user-management/table/users/OrgUsersTable.svelte b/web-admin/src/features/organizations/user-management/table/users/OrgUsersTable.svelte index 486efe927500..f6c02e31defa 100644 --- a/web-admin/src/features/organizations/user-management/table/users/OrgUsersTable.svelte +++ b/web-admin/src/features/organizations/user-management/table/users/OrgUsersTable.svelte @@ -154,15 +154,19 @@ }; - + +
+ +
diff --git a/web-admin/src/features/projects/status/overview/ProjectClone.svelte b/web-admin/src/features/projects/status/overview/ProjectClone.svelte index 448163b28d14..3a68cf1b3ed3 100644 --- a/web-admin/src/features/projects/status/overview/ProjectClone.svelte +++ b/web-admin/src/features/projects/status/overview/ProjectClone.svelte @@ -30,7 +30,11 @@ {/snippet} - +
{m.status_clone_description()} diff --git a/web-admin/src/features/settings/SettingsItemContainer.svelte b/web-admin/src/features/settings/SettingsItemContainer.svelte index 5035c44cfe0d..e72141a4d709 100644 --- a/web-admin/src/features/settings/SettingsItemContainer.svelte +++ b/web-admin/src/features/settings/SettingsItemContainer.svelte @@ -29,7 +29,7 @@ diff --git a/web-common/src/lib/i18n/messages/en.json b/web-common/src/lib/i18n/messages/en.json index 877edefec849..76f6f668189b 100644 --- a/web-common/src/lib/i18n/messages/en.json +++ b/web-common/src/lib/i18n/messages/en.json @@ -1280,6 +1280,8 @@ } ], "pivot_collapse_row": "Collapse row", + "pivot_desktop_only_message": "Switch to a larger screen to use the pivot view.", + "pivot_desktop_only_title": "Pivot is available on desktop", "pivot_dim_label": [ { "declarations": ["input count", "local countPlural = count: plural"], diff --git a/web-common/src/lib/i18n/messages/es.json b/web-common/src/lib/i18n/messages/es.json index b733f2ea6c44..18f2aa7825c8 100644 --- a/web-common/src/lib/i18n/messages/es.json +++ b/web-common/src/lib/i18n/messages/es.json @@ -1289,6 +1289,8 @@ } ], "pivot_collapse_row": "Contraer fila", + "pivot_desktop_only_message": "Cambia a una pantalla más grande para usar la vista de pivote.", + "pivot_desktop_only_title": "El pivote está disponible en escritorio", "pivot_dim_label": [ { "declarations": ["input count", "local countPlural = count: plural"], From e7a286918269c3a13097a76610130b7a6b6d4bbd Mon Sep 17 00:00:00 2001 From: dfliess Date: Mon, 13 Jul 2026 18:10:49 +0200 Subject: [PATCH 6/9] fix(canvas): let canvas rows shrink below 420px Canvas rows kept a fixed minimum that forced horizontal overflow on phones; they now shrink with the viewport. --- web-common/src/features/canvas/CanvasDashboardWrapper.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-common/src/features/canvas/CanvasDashboardWrapper.svelte b/web-common/src/features/canvas/CanvasDashboardWrapper.svelte index cab84a40e072..cd632beaef86 100644 --- a/web-common/src/features/canvas/CanvasDashboardWrapper.svelte +++ b/web-common/src/features/canvas/CanvasDashboardWrapper.svelte @@ -119,7 +119,7 @@
From 3e7c6ea60edc8ece42d82df0044c607f051b42dd Mon Sep 17 00:00:00 2001 From: dfliess Date: Mon, 13 Jul 2026 18:12:03 +0200 Subject: [PATCH 7/9] fix(web-admin): fit embed shells and markdown on narrow viewports Embed layouts drop their fixed minimum width, the embed header wraps, wide markdown tables scroll inside their own container, and the table toolbar wraps below sm. --- web-admin/src/features/embeds/EmbedHeader.svelte | 2 +- web-admin/src/routes/-/embed/+layout.svelte | 2 +- web-common/src/components/markdown/Markdown.svelte | 4 ++++ web-common/src/components/table-toolbar/TableToolbar.svelte | 4 +++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/web-admin/src/features/embeds/EmbedHeader.svelte b/web-admin/src/features/embeds/EmbedHeader.svelte index d86add076e16..3745351c6365 100644 --- a/web-admin/src/features/embeds/EmbedHeader.svelte +++ b/web-admin/src/features/embeds/EmbedHeader.svelte @@ -56,7 +56,7 @@ {#if $isErrorStoreEmpty && shouldRender}
{#if navigationEnabled} -