app/lib/theme.ts
47 lines and 5 functions in DrDustinEdwards/dustinedwards-info, at commit 1590ba4 on 2026-10-24. This file on GitHub.
Functions
| Function | Lines | Complexity | Covered | CRAP | Mutation |
|---|---|---|---|---|---|
| isWritableTheme | 19 to 21 | 2 | 100% | OkCRAP 2 | No data: no mutants in this code |
| themeFromRequest | 24 to 41 | 6 | 94% | WatchCRAP 6 | No data: no mutants in this code |
| themeAttribute | 44 to 46 | 2 | 100% | OkCRAP 2 | No data: no mutants in this code |
| colorSchemeMeta | 52 to 54 | 2 | 100% | OkCRAP 2 | No data: no mutants in this code |
| serializeThemeCookie | 56 to 58 | 1 | 33% | OkCRAP 1.3 | No data: no mutants in this code |
Imports
It imports no other module of this repo. Imported by app/enhance/theme.ts, app/root.tsx, app/routes/theme.ts, workers/app.ts.
Source
/**
* A cookie, not localStorage, so the server writes the right `data-theme` into the first byte and
* nothing flashes.
*/
const THEME_COOKIE = "theme";
const THEME_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
// "system" is a cache-key value but not writable: no control posts it and `/theme` refuses it.
const THEMES = ["light", "dark", "system"] as const;
type Theme = (typeof THEMES)[number];
const WRITABLE_THEMES = ["light", "dark"] as const;
export type WritableTheme = (typeof WRITABLE_THEMES)[number];
export function isWritableTheme(value: unknown): value is WritableTheme {
return typeof value === "string" && (WRITABLE_THEMES as readonly string[]).includes(value);
}
// Anything unrecognised reads as "system": a bad cookie should cost the default theme, never the page.
export function themeFromRequest(request: Request): Theme {
const header = request.headers.get("cookie");
if (!header) return "system";
for (const part of header.split(";")) {
const [name, ...rest] = part.trim().split("=");
if (name !== THEME_COOKIE) continue;
// `decodeURIComponent` throws, and this builds the cache key on every request: unguarded, a
// bad cookie is a 500 on every page.
let value;
try {
value = decodeURIComponent(rest.join("="));
} catch {
return "system";
}
return isWritableTheme(value) ? value : "system";
}
return "system";
}
// Omitted for "system", which hands the decision to prefers-color-scheme.
export function themeAttribute(theme: Theme): "light" | "dark" | undefined {
return theme === "system" ? undefined : theme;
}
/**
* `data-theme` means nothing to the browser until the CSS parses, so without this meta the canvas
* painted between documents is light.
*/
export function colorSchemeMeta(theme: Theme): "light" | "dark" | "light dark" {
return theme === "system" ? "light dark" : theme;
}
export function serializeThemeCookie(theme: Theme): string {
return `${THEME_COOKIE}=${theme}; Path=/; Max-Age=${THEME_COOKIE_MAX_AGE}; SameSite=Lax; Secure`;
}