diff --git a/docs/VERIFICATION.md b/docs/VERIFICATION.md index 1d83cc8..5b8337a 100644 --- a/docs/VERIFICATION.md +++ b/docs/VERIFICATION.md @@ -77,6 +77,10 @@ Nach der gestalterischen Überarbeitung wurden TypeScript-Prüfung und Produktio - Visuell angesehen: neue Desktop-Rezeptkarten für Schritte, Smartphone-Rezeptseite mit Schrittbildern, dunkle Desktop-Schrittkarte und mobiler Kochmodus. Die Rezept-/Kochmodus-Screenshots unter `docs/screenshots/` wurden aktualisiert; zusätzliche Bild-/Listenansichten liegen in den Playwright-Testartefakten. - Die JSON-Vorlage in `docs/CHATGPT-PROMPT.md` wurde mit dem tatsächlichen Importparser geprüft; das öffentliche JSON-Schema und die Beispiele wurden neu erzeugt. +### Nachprüfung: mobile Rezeptbreite + +Der mobile Überlauf wurde am veröffentlichten Pljeskavica-Rezept reproduziert: lange Bildquellen-URLs in den Tipps vergrößerten die minimale Grid-Spaltenbreite und damit auch die Zutatenliste. Die Spalte verwendet nun `minmax(0, 1fr)`, ihre Kinder dürfen schrumpfen und lange Texte umbrechen. Mit dem echten Rezept und dem korrigierten Stylesheet wurden 320, 360, 390, 430 und 768 Pixel Breite ohne Überlauf geprüft. Produktionsbuild und alle 15 öffentlichen Browserabläufe bestanden. Der Regressionstest enthält eine lange Quellen-URL und vergleicht die Inhaltsbreite mit `document.documentElement.clientWidth`; `innerWidth` kann sich auf mobilen Browsern selbst mit dem Überlauf vergrößern und ist dafür ungeeignet. + ## Präzise Grenzen - Kein Test auf physischem Smartphone, Safari oder Firefox; keine tatsächliche iOS-/Android-Installation über das Betriebssystem-Installationsmenü. diff --git a/docs/screenshots/recipe-desktop.png b/docs/screenshots/recipe-desktop.png index 8d5089e..6e2763d 100644 Binary files a/docs/screenshots/recipe-desktop.png and b/docs/screenshots/recipe-desktop.png differ diff --git a/docs/screenshots/recipe-mobile.png b/docs/screenshots/recipe-mobile.png index 57c2b8b..071aee6 100644 Binary files a/docs/screenshots/recipe-mobile.png and b/docs/screenshots/recipe-mobile.png differ diff --git a/docs/screenshots/recipe-tablet.png b/docs/screenshots/recipe-tablet.png index f561c48..2e65a0c 100644 Binary files a/docs/screenshots/recipe-tablet.png and b/docs/screenshots/recipe-tablet.png differ diff --git a/src/app/globals.css b/src/app/globals.css index 6ff677a..6346d26 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -869,6 +869,10 @@ small, grid-template-columns: 350px minmax(0, 1fr); gap: 65px; align-items: start; + overflow-wrap: anywhere; +} +.recipe-columns > * { + min-width: 0; } .ingredients-panel { background: var(--surface); @@ -997,6 +1001,7 @@ small, .ingredient-name { font-size: 12px; flex: 1; + min-width: 0; } .ingredient-name small { display: block; @@ -1138,6 +1143,11 @@ small, } .tip-card svg { color: var(--green); + flex-shrink: 0; +} +.tip-card > div { + min-width: 0; + flex: 1; } .tip-card h3 { font-size: 23px; @@ -2304,7 +2314,7 @@ input[type='checkbox'] { flex: 1; } .recipe-columns { - grid-template-columns: 1fr; + grid-template-columns: minmax(0, 1fr); gap: 35px; } .ingredients-panel { diff --git a/tests/e2e/public.spec.ts b/tests/e2e/public.spec.ts index 404cfd4..541f3ed 100644 --- a/tests/e2e/public.spec.ts +++ b/tests/e2e/public.spec.ts @@ -21,6 +21,7 @@ test('external images and structured steps survive import, editing, scaling and title: `Bilderküche ${info.project.name}`, image: cover, images: ['https://images.example.org/gallery.jpg'], + tips: 'Bildquelle: https://commons.wikimedia.org/wiki/File:Pljeskavica_cheese_chili_with_a_very_long_unbroken_filename.jpg\n\nLizenz: https://creativecommons.org/licenses/by-sa/4.0/', steps: pasta.steps.map((s, n) => n === 0 ? { @@ -86,9 +87,21 @@ test('external images and structured steps survive import, editing, scaling and await page.getByRole('spinbutton', { name: 'Portionen', exact: true }).fill('4'); await expect(first.locator('.instruction-ingredient').first()).toHaveText('80 g Basilikum'); await expect(first.locator('.step-supplies')).toContainText('80 g Basilikum'); - expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe( - true, - ); + expect( + await page + .locator('.recipe-columns') + .evaluate((el) => + [...el.querySelectorAll('*')].every( + (child) => + child.getBoundingClientRect().right <= document.documentElement.clientWidth + 1, + ), + ), + ).toBe(true); + expect( + await page.evaluate( + () => document.documentElement.scrollWidth <= document.documentElement.clientWidth, + ), + ).toBe(true); await page.screenshot({ path: info.outputPath('structured-steps.png'), fullPage: true }); await page.getByRole('button', { name: 'Theme wechseln', exact: true }).click(); await first.screenshot({ path: info.outputPath('structured-step-dark.png') }); @@ -98,9 +111,11 @@ test('external images and structured steps survive import, editing, scaling and ); await expect(page.locator('.cooking-step .step-images img')).toHaveCount(2); await expect(page.locator('.cooking-step')).toHaveCSS('opacity', '1'); - expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe( - true, - ); + expect( + await page.evaluate( + () => document.documentElement.scrollWidth <= document.documentElement.clientWidth, + ), + ).toBe(true); await page.screenshot({ path: info.outputPath('structured-cooking.png'), fullPage: true }); expect(errors).toEqual([]); } finally { @@ -147,7 +162,11 @@ test('editor remains usable on the current viewport in dark mode', async ({ page await expect(page.getByRole('textbox', { name: 'Zutat 1', exact: true })).toHaveValue('Karotten'); await expect(page.locator('.ingredient-entry').nth(0)).toContainText('optional'); await expect(page.locator('.ingredient-entry').nth(1)).toContainText('geschält und gewürfelt'); - expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); + expect( + await page.evaluate( + () => document.documentElement.scrollWidth <= document.documentElement.clientWidth, + ), + ).toBe(true); await page.screenshot({ path: `docs/screenshots/editor-dark-${info.project.name}.png`, fullPage: true, @@ -166,7 +185,11 @@ test('editor remains usable on the current viewport in dark mode', async ({ page ); } await page.getByRole('button', { name: 'Live-Vorschau', exact: true }).click(); - expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); + expect( + await page.evaluate( + () => document.documentElement.scrollWidth <= document.documentElement.clientWidth, + ), + ).toBe(true); }); test('public library, shareable search, favorites, themes and responsive layouts', async ({ page, @@ -186,7 +209,11 @@ test('public library, shareable search, favorites, themes and responsive layouts fullPage: true, animations: 'disabled', }); - expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); + expect( + await page.evaluate( + () => document.documentElement.scrollWidth <= document.documentElement.clientWidth, + ), + ).toBe(true); await page.getByRole('textbox', { name: 'Rezepte suchen' }).fill('Basilikum'); await expect(page.locator('.recipe-card')).toHaveCount(1); await expect(page).toHaveURL(/q=Basilikum/); @@ -230,7 +257,11 @@ test('portion calculator, ingredient anchor, shopping list and print content', a path: `docs/screenshots/recipe-${info.project.name}.png`, fullPage: true, }); - expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); + expect( + await page.evaluate( + () => document.documentElement.scrollWidth <= document.documentElement.clientWidth, + ), + ).toBe(true); await page.getByRole('button', { name: 'Zur Einkaufsliste hinzufügen' }).click(); await page.goto('/einkaufsliste'); await expect(page.locator('.shopping-list')).toContainText('400 g');