From 494640c1a87d92674f0209da7773faac4935cd82 Mon Sep 17 00:00:00 2001 From: Kristian Wind Date: Mon, 17 Aug 2026 23:24:49 +0200 Subject: [PATCH] fix(mobile): the drawer no longer pushes the page sideways MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The task drawer was `width: 100vw`. A viewport unit includes the scrollbar gutter and a percentage does not, so the drawer came out a few pixels wider than the page it was pinned to and the whole document could be shoved sideways. It is `100%` now, and at phone width it is stated as four insets instead — a box defined by the viewport's own edges cannot be a rounding error wider than it. The scrolling body gets `min-width: 0` and `overflow-x: hidden` too, so a pasted URL or a long unbroken word in a comment cannot stretch the flex column and take the page with it. Writing the test for that found another one: creating a project from the sidebar navigated to it and left the drawer covering the screen. Every link in there closes it on the way out; the form did not, because it calls `goto` directly. Now it does the same thing for the same reason. The phone test checks the document cannot be scrolled horizontally, both with the drawer shut and with it open. Co-Authored-By: Claude Opus 5 --- web/e2e/smoke.spec.js | 18 ++++++++++++++---- web/src/lib/components/Sidebar.svelte | 8 +++++++- web/src/lib/components/TaskDetail.svelte | 18 ++++++++++++++++-- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/web/e2e/smoke.spec.js b/web/e2e/smoke.spec.js index 2bab4f7..6642265 100644 --- a/web/e2e/smoke.spec.js +++ b/web/e2e/smoke.spec.js @@ -836,10 +836,20 @@ test('projektsiden er brugbar på en telefon', async ({ page }) => { expect(box.height, 'overskriften er brudt op i én bogstavkolonne').toBeLessThan(120); // And nothing sticks out past the right edge of the page. - const overflow = await page.evaluate( - () => document.documentElement.scrollWidth - document.documentElement.clientWidth - ); - expect(overflow, 'siden kan scrolles vandret').toBeLessThanOrEqual(1); + const sideways = () => + page.evaluate( + () => document.documentElement.scrollWidth - document.documentElement.clientWidth + ); + expect(await sideways(), 'siden kan scrolles vandret').toBeLessThanOrEqual(1); + + // The task drawer, too. It was `width: 100vw`, which includes the scrollbar + // gutter where a percentage does not, so the drawer came out wider than the + // page and the whole document could be pushed sideways. + await page.getByLabel('Ny opgave').fill('mal væggen'); + await page.getByLabel('Ny opgave').press('Enter'); + await page.getByText('mal væggen', { exact: true }).click(); + await expect(page.getByRole('complementary', { name: 'Opgave' })).toBeVisible(); + expect(await sideways(), 'opgaveruden skubber siden sidelæns').toBeLessThanOrEqual(1); expect(trouble).toEqual([]); }); diff --git a/web/src/lib/components/Sidebar.svelte b/web/src/lib/components/Sidebar.svelte index 0b0e54f..7f772f4 100644 --- a/web/src/lib/components/Sidebar.svelte +++ b/web/src/lib/components/Sidebar.svelte @@ -18,7 +18,13 @@ newName = ''; adding = false; const project = await app.createProject(name); - if (project) goto(`/projekt/${project.id}`); + if (!project) return; + // Closes the drawer as well as navigating. On a phone the sidebar is an + // overlay, and creating a project moved the page underneath it while leaving + // it covering the screen — every link in here closes it, and this makes the + // same move for the same reason. + onnavigate?.(); + goto(`/projekt/${project.id}`); } async function signOut() { diff --git a/web/src/lib/components/TaskDetail.svelte b/web/src/lib/components/TaskDetail.svelte index aff1d3f..fefa084 100644 --- a/web/src/lib/components/TaskDetail.svelte +++ b/web/src/lib/components/TaskDetail.svelte @@ -575,7 +575,12 @@ .drawer { position: fixed; inset: 0 0 0 auto; - width: min(480px, 100vw); + /* `100%` rather than `100vw`. For a fixed element a percentage resolves + against the initial containing block, which excludes the scrollbar gutter + — `100vw` does not, so the drawer was a few pixels wider than the page and + the whole document could be pushed sideways. */ + width: min(480px, 100%); + max-width: 100%; z-index: 80; display: flex; flex-direction: column; @@ -625,7 +630,12 @@ .scroll { flex: 1; + min-width: 0; overflow-y: auto; + /* Nothing inside may widen the drawer. A pasted URL in a comment or a long + unbroken word would otherwise stretch the flex column and take the page + with it. */ + overflow-x: hidden; overscroll-behavior: contain; padding: var(--s4) var(--s4) var(--s8); display: flex; @@ -883,8 +893,12 @@ } @media (max-width: 560px) { + /* Full width, stated as four insets rather than a width: the box is then + defined by the viewport's edges and cannot be a rounding error wider than + the thing it is pinned to. */ .drawer { - width: 100vw; + inset: 0; + width: auto; border-left: 0; } }