Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions frontend/README-VERCEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ a local CLI deployment is not the routine production path.

After promotion, confirm the production deployment references the intended
commit. Open the canonical domain and verify the theme toggle changes theme
and the client scene mounts. Build success and analytics events alone do not
establish that client initialization worked.
and persists after reload, the specimen plates render, and `/support` shows its
buy links. Build success and analytics events alone do not establish that client
initialization worked.

`npm run og` regenerates the share cards in `public/og*` with playwright-core
and the locally installed Google Chrome; it isn't part of the build.

## Rendering and security headers

Expand Down
2 changes: 1 addition & 1 deletion frontend/app/(site)/support/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from "@/lib/support";

const description =
"Back CodesWhat through GitHub Sponsors or a Stripe payment link, see what each project costs to run, and what happens to a project that can't cover its costs.";
"Back CodesWhat through GitHub Sponsors or a Stripe payment link, and see what each project costs to run.";

const SUPPORT_OG = {
url: "/og/support.png",
Expand Down
18 changes: 10 additions & 8 deletions frontend/lib/theme-init-script.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// Runs before first paint (nonce'd in the root layout) so the page never flashes the wrong
// paper. An explicit choice saved by the header toggle wins; otherwise follow the OS, and
// fall back to light. data-theme on <html> is what the Catalogue tokens key off.
export const THEME_INIT_SCRIPT = `try {
var saved = localStorage.getItem('theme')
var theme = saved === 'light' || saved === 'dark'
? saved
: (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
export const THEME_INIT_SCRIPT = `(function () {
var saved = null
try { saved = localStorage.getItem('theme') } catch (_) {}
var theme = 'light'
try {
theme = saved === 'light' || saved === 'dark'
? saved
: (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
} catch (_) {}
document.documentElement.dataset.theme = theme
document.documentElement.style.colorScheme = theme
} catch (_) {
document.documentElement.dataset.theme = 'light'
}`;
})()`;
17 changes: 17 additions & 0 deletions frontend/test/site-browser.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,23 @@ test("light is the default paper when the system has no dark preference", {
assert.equal(await darkPage.locator("html").getAttribute("data-theme"), "dark");
});

test("a dark system preference still wins when storage is blocked", {
timeout: 15_000,
}, async (t) => {
const context = await browser.newContext({ colorScheme: "dark" });
t.after(() => context.close());
await context.addInitScript(() => {
Object.defineProperty(window, "localStorage", {
get() {
throw new DOMException("blocked", "SecurityError");
},
});
});
const page = await context.newPage();
await page.goto(baseUrl, { waitUntil: "domcontentloaded" });
assert.equal(await page.locator("html").getAttribute("data-theme"), "dark");
});

test("a project page renders its specimen and unknown slugs 404", {
timeout: 20_000,
}, async (t) => {
Expand Down
Loading