Custom Frutiger Aero 404

app/not-found.tsx renders a standalone screen: the root layout's ice
scene is the backdrop, the glass card/rim/bubbles come from the shared
recipes (full Aero with that theme on, frosted glass in light/dark),
and the mascot is the scene's glossy ice orb at full strength with a
snowflake and a "404" chip. Two ways out - back to the library and
into the chat - both 44px targets, en + cs.

The regression suite pins the 404 status, both links and the Czech
copy; the pixel-contrast scan covers the screen in all three themes.
The service worker no longer caches non-OK navigations as the offline
shell (a typo'd URL used to write the error page into the shell
cache). Version 0.5.6, SW -> freezmo-v78.
This commit is contained in:
Martin Dvořák 2026-10-14 15:27:47 +00:00
commit 7e42b03f7e
9 changed files with 137 additions and 4 deletions

View file

@ -139,5 +139,26 @@ const browser = await launchBrowser();
await context.close();
}
/* 6. custom 404 ------------------------------------------------------ */
{
const { context, page } = await makePage(browser, { width: 390, height: 844, deviceScaleFactor: 1 });
await seed(page, { decks: [DECK] });
const response = await page.goto(`${BASE}/no-such-page`, { waitUntil: "networkidle" });
await page.waitForTimeout(800);
check("404: an unknown URL answers 404", response !== null && response.status() === 404);
check("404: the custom screen renders", await page.getByRole("heading", { name: "This page melted away" }).isVisible());
check("404: it offers the way home", await page.getByRole("link", { name: "Back to library" }).isVisible());
check("404: the chat escape hatch is there", await page.getByRole("link", { name: "Ask the chat instead" }).isVisible());
check("404: it rides on the glass card", (await page.locator("main .card").count()) === 1);
await context.close();
const cs = await makePage(browser, { width: 390, height: 844, deviceScaleFactor: 1 });
await seed(cs.page, { decks: [DECK], locale: "cs" });
await cs.page.goto(`${BASE}/no-such-page`, { waitUntil: "networkidle" });
await cs.page.waitForTimeout(800);
check("404: Czech copy renders", await cs.page.getByRole("heading", { name: "Tahle stránka se rozpustila" }).isVisible());
await cs.context.close();
}
await browser.close();
finish();