diff --git a/web/e2e/smoke.spec.js b/web/e2e/smoke.spec.js
index 5a5e47d..2bab4f7 100644
--- a/web/e2e/smoke.spec.js
+++ b/web/e2e/smoke.spec.js
@@ -804,6 +804,46 @@ test('en rolle kan rettes uden at fjerne personen', async ({ browser, page }) =>
expect(trouble).toEqual([]);
});
+/**
+ * The project page on a phone.
+ *
+ * Every other test here runs at desktop width, and this is what that misses: the
+ * header is a flex row of a title and four controls, and 390px does not have room
+ * for them. Without wrapping, the heading was squeezed to its min-content — which
+ * with `overflow-wrap: anywhere` is one character — so a project called
+ * "Skovvænget" rendered as a vertical column of letters while the buttons ran off
+ * the right edge. It looked like a broken font.
+ */
+test('projektsiden er brugbar på en telefon', async ({ page }) => {
+ const trouble = watchForTrouble(page);
+ await page.setViewportSize({ width: 390, height: 844 });
+ await page.goto('/');
+
+ // The sidebar is a drawer at this width, so the project is reached through it.
+ await page.getByRole('button', { name: 'Vis menu' }).click();
+ const sidebar = page.getByRole('navigation', { name: 'Hovedmenu' });
+ await sidebar.getByRole('button', { name: 'Nyt projekt' }).click();
+ await sidebar.getByLabel('Projektnavn').fill('Skovvænget');
+ await sidebar.getByLabel('Projektnavn').press('Enter');
+
+ const heading = page.getByRole('heading', { name: 'Skovvænget' });
+ await expect(heading).toBeVisible();
+
+ const box = await heading.boundingBox();
+ // Wide enough to hold the word, and short enough not to be a column of
+ // letters. One line of "Skovvænget" at --text-2xl is nowhere near 200px tall.
+ expect(box.width, 'overskriften er klemt sammen').toBeGreaterThan(120);
+ 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);
+
+ expect(trouble).toEqual([]);
+});
+
/**
* The MCP connector address must not fall through to the app shell.
*
diff --git a/web/src/routes/projekt/[id]/+page.svelte b/web/src/routes/projekt/[id]/+page.svelte
index 672c8ac..5bca5ee 100644
--- a/web/src/routes/projekt/[id]/+page.svelte
+++ b/web/src/routes/projekt/[id]/+page.svelte
@@ -548,7 +548,10 @@
ondrop={(e) => onSectionDrop(e, '')}
>
Uden sektion
{/if} @@ -664,17 +667,28 @@ max-width: 1400px; } + /* Wraps, and the title keeps a floor. + * + * Without both, a phone squeezes the heading to its min-content — which with + * `overflow-wrap: anywhere` is *one character*, so "Skovvænget" came out as a + * vertical column of letters while the buttons ran off the right edge. Four + * controls beside a title is more than 390px has, so they belong on the next + * line rather than in a race for the same one. + */ header { display: flex; align-items: center; - gap: var(--s4); + gap: var(--s3); + flex-wrap: wrap; margin-bottom: var(--s5); } h1 { font-size: var(--text-2xl); - flex: 1; - min-width: 0; + /* 8ch rather than 0: the basis is what stops flex shrinking it past the + point where the wrapping below is the better answer. */ + flex: 1 1 8ch; + min-width: 8ch; overflow-wrap: anywhere; } @@ -694,8 +708,8 @@ /* Matches the heading it stands in for, so the swap does not move the page. */ .title { - flex: 1; - min-width: 0; + flex: 1 1 8ch; + min-width: 8ch; font-size: var(--text-2xl); font-weight: 560; letter-spacing: -0.015em; diff --git a/web/src/routes/upcoming/+page.svelte b/web/src/routes/upcoming/+page.svelte index f4942dc..e822da2 100644 --- a/web/src/routes/upcoming/+page.svelte +++ b/web/src/routes/upcoming/+page.svelte @@ -135,17 +135,21 @@ max-width: 1400px; } + /* Wraps, for the same reason the project header does: a title and a switcher + are more than a phone's width, and the switcher belongs underneath rather + than squeezing the heading. */ header { display: flex; align-items: center; - gap: var(--s4); + gap: var(--s3); + flex-wrap: wrap; margin-bottom: var(--s5); } h1 { font-size: var(--text-2xl); - flex: 1; - min-width: 0; + flex: 1 1 8ch; + min-width: 8ch; } .views {