From 38ccb852f44079c010bf6bcb6f6e529171f70512 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 46bd1065e38d345195039a3f209dfa99f4d675bb 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 | 1 + .../-/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, 63 insertions(+), 40 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 79e18b03f9bc..a0078ecd9caf 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 aaa2057b59ea..50b9ee6bfec1 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 cea17aa6eceb..51fe4c00a5de 100644 --- a/web-common/src/lib/i18n/messages/en.json +++ b/web-common/src/lib/i18n/messages/en.json @@ -1436,6 +1436,8 @@ "pivot_auto": "Auto", "pivot_chip_aria": "{title} pivot chip", "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 fdbea441c76d..e87a893bdd8e 100644 --- a/web-common/src/lib/i18n/messages/es.json +++ b/web-common/src/lib/i18n/messages/es.json @@ -1445,6 +1445,8 @@ "pivot_auto": "Automático", "pivot_chip_aria": "Chip de pivote {title}", "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 fa6e4deb4e53289ea25b54c5f238b2d8077fc0d0 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 71f40f7adcb13fa2b64f0630bd0eb9c2f26ae763 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 b15601ca1a16..0f4ee6235a98 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 navigationBarEnabled} -