From b8f9d4c3f5287a859786fbb9cea86a35e471bf79 Mon Sep 17 00:00:00 2001 From: Asterios Raptis Date: Tue, 30 Jun 2026 10:26:53 +0200 Subject: [PATCH] fix(responsive): mobile overflow hardening for new features + 375px e2e (#43) No deliberate responsive pass had been done for this cycle's features. Most are responsive by default (viewport-relative widths, flex-wrap, auto-fill grids, safe-area); two overflow risks remained: - QrCodeModal: the 240px QR image is now width:100% / max-width:240px / aspect-ratio 1 (fits screens narrower than 240px+padding). - PWA UpdatePrompt/InstallPrompt banner: the message+buttons row now flex-wraps so it never overflows on narrow screens. Adds e2e/smoke/responsive-mobile.spec.ts: at a 375px viewport, asserts the command palette, About + QR modal, and Appearance/Data tabs produce no horizontal overflow (scrollWidth <= viewport). Run via the e2e webServer. Affected unit tests green; theme-token guard, eslint, build OK. Closes #43 --- e2e/smoke/responsive-mobile.spec.ts | 48 +++++++++++++++++++ .../src/components/QrCodeModal.module.css | 11 +++-- .../components/pwa/UpdatePrompt.module.css | 4 +- 3 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 e2e/smoke/responsive-mobile.spec.ts diff --git a/e2e/smoke/responsive-mobile.spec.ts b/e2e/smoke/responsive-mobile.spec.ts new file mode 100644 index 0000000..d984b42 --- /dev/null +++ b/e2e/smoke/responsive-mobile.spec.ts @@ -0,0 +1,48 @@ +// TEMPLATE: adaptable example. +/** + * E2E smoke: the new features fit a 375px mobile viewport. + * + * Catches the horizontal-overflow class of responsive bug (a fixed-width + * element wider than the screen) on the surfaces added this cycle. The + * assertion is document-wide: scrollWidth must not exceed the viewport. + */ +import {test, expect, cancelDialog} from "../fixtures/base"; +import type {Page} from "@playwright/test"; + +test.use({viewport: {width: 375, height: 812}}); + +/** Horizontal overflow in px (<= 1 means the page fits the viewport). */ +async function horizontalOverflow(page: Page): Promise { + return page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth); +} + +test.describe("Mobile 375px - new surfaces fit the viewport", () => { + test("command palette overlay fits", async ({page}) => { + await page.goto("/"); + await page.keyboard.press("Control+k"); + await expect(page.getByTestId("command-palette")).toBeVisible(); + expect(await horizontalOverflow(page)).toBeLessThanOrEqual(1); + }); + + test("About tab + QR modal fit", async ({page}) => { + await page.goto("/settings?tab=about"); + await expect(page.getByTestId("about-tab")).toBeVisible(); + expect(await horizontalOverflow(page)).toBeLessThanOrEqual(1); + await page.getByTestId("about-share-button").click(); + await expect(page.getByTestId("qr-modal")).toBeVisible(); + expect(await horizontalOverflow(page)).toBeLessThanOrEqual(1); + }); + + test("Appearance + Data tabs fit", async ({page}) => { + await page.goto("/settings?tab=appearance"); + await expect(page.getByTestId("appearance-settings")).toBeVisible(); + expect(await horizontalOverflow(page)).toBeLessThanOrEqual(1); + + await page.goto("/settings?tab=data"); + await expect(page.getByTestId("data-settings")).toBeVisible(); + expect(await horizontalOverflow(page)).toBeLessThanOrEqual(1); + // Destructive action must still confirm on mobile; cancel to avoid reload. + await page.getByTestId("data-clear-cache").click(); + await cancelDialog(page); + }); +}); diff --git a/frontend/src/components/QrCodeModal.module.css b/frontend/src/components/QrCodeModal.module.css index a74b527..a046234 100644 --- a/frontend/src/components/QrCodeModal.module.css +++ b/frontend/src/components/QrCodeModal.module.css @@ -54,15 +54,18 @@ } .qr { - width: 240px; - height: 240px; + width: 100%; + max-width: 240px; + height: auto; + aspect-ratio: 1 / 1; background: #ffffff; /* token-exempt: QR must stay black-on-white to scan */ border-radius: var(--radius-sm); } .qrPlaceholder { - width: 240px; - height: 240px; + width: 100%; + max-width: 240px; + aspect-ratio: 1 / 1; background: var(--bg-hover, var(--border)); border-radius: var(--radius-sm); } diff --git a/frontend/src/components/pwa/UpdatePrompt.module.css b/frontend/src/components/pwa/UpdatePrompt.module.css index 502c7ed..8c24228 100644 --- a/frontend/src/components/pwa/UpdatePrompt.module.css +++ b/frontend/src/components/pwa/UpdatePrompt.module.css @@ -5,8 +5,10 @@ transform: translateX(-50%); z-index: 9999; display: flex; + flex-wrap: wrap; align-items: center; - gap: 0.75rem; + justify-content: flex-end; + gap: 0.5rem 0.75rem; max-width: calc(100vw - 2rem); padding: 0.625rem 0.875rem; background: var(--bg-card);