fix(fabro-web): default unsaved theme selection to dark

Remove the system color-scheme fallback from the web UI theme boot path.
Fabro now uses a saved light/dark preference when present and otherwise
starts in dark mode by default. Add a regression test for the shared
theme selection helper and refresh the built web assets.
This commit is contained in:
Bryan Helmkamp 2026-04-08 12:10:34 -04:00
parent c4b51e44ea
commit 8e26d30460
No known key found for this signature in database
9 changed files with 147 additions and 111 deletions

View file

@ -0,0 +1,24 @@
import { describe, expect, test } from "bun:test";
import { buildThemeBootScript, resolveTheme } from "./theme-selection";
describe("resolveTheme", () => {
test("defaults to dark when no saved theme exists", () => {
expect(resolveTheme(null)).toBe("dark");
expect(resolveTheme("system")).toBe("dark");
});
test("uses the saved theme when it is valid", () => {
expect(resolveTheme("light")).toBe("light");
expect(resolveTheme("dark")).toBe("dark");
});
});
describe("buildThemeBootScript", () => {
test("defaults the boot script to dark without using system preference", () => {
const script = buildThemeBootScript();
expect(script).toContain('localStorage.getItem("fabro-theme")');
expect(script).toContain('t="dark"');
expect(script).not.toContain("matchMedia");
});
});

View file

@ -0,0 +1,16 @@
type Theme = "light" | "dark";
const STORAGE_KEY = "fabro-theme";
function resolveTheme(storedTheme: string | null): Theme {
return storedTheme === "light" || storedTheme === "dark"
? storedTheme
: "dark";
}
function buildThemeBootScript(storageKey = STORAGE_KEY) {
return `(function(){try{var t=localStorage.getItem("${storageKey}");if(t!=="light"&&t!=="dark")t="dark";document.documentElement.classList.add(t)}catch(e){document.documentElement.classList.add("dark")}})()`;
}
export { STORAGE_KEY, buildThemeBootScript, resolveTheme };
export type { Theme };

View file

@ -1,16 +1,10 @@
import { createContext, useCallback, useContext, useEffect, useState } from "react";
type Theme = "light" | "dark";
const STORAGE_KEY = "fabro-theme";
import { STORAGE_KEY, resolveTheme } from "./theme-selection";
import type { Theme } from "./theme-selection";
function getInitialTheme(): Theme {
if (typeof window === "undefined") return "dark";
const stored = localStorage.getItem(STORAGE_KEY);
if (stored === "light" || stored === "dark") return stored;
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
return resolveTheme(localStorage.getItem(STORAGE_KEY));
}
function applyThemeClass(theme: Theme) {

View file

@ -1,8 +1,9 @@
import { isRouteErrorResponse, Outlet } from "react-router";
import { ThemeProvider } from "./lib/theme";
import { buildThemeBootScript } from "./lib/theme-selection";
import "./app.css";
const themeScript = `(function(){try{var t=localStorage.getItem("fabro-theme");if(t!=="light"&&t!=="dark")t=window.matchMedia("(prefers-color-scheme:dark)").matches?"dark":"light";document.documentElement.classList.add(t)}catch(e){document.documentElement.classList.add("dark")}})()`;
const themeScript = buildThemeBootScript();
export default function Root() {
return (

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -14,12 +14,12 @@
href="https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=JetBrains+Mono:wght@400;500;600&display=swap"
/>
<script>
(function(){try{var t=localStorage.getItem("fabro-theme");if(t!=="light"&&t!=="dark")t=window.matchMedia("(prefers-color-scheme:dark)").matches?"dark":"light";document.documentElement.classList.add(t)}catch(e){document.documentElement.classList.add("dark")}})()
(function(){try{var t=localStorage.getItem("fabro-theme");if(t!=="light"&&t!=="dark")t="dark";document.documentElement.classList.add(t)}catch(e){document.documentElement.classList.add("dark")}})()
</script>
<link rel="stylesheet" href="/assets/app.css" />
<link rel="stylesheet" href="/assets/entry-t2wvsf4c.css" />
</head>
<body class="h-full">
<body class="h-full font-sans antialiased">
<div id="root"></div>
<script type="module" src="/assets/chunk-etxafnd6.js"></script>
<script type="module" src="/assets/chunk-kv06br00.js"></script>
@ -61,7 +61,7 @@
<script type="module" src="/assets/chunk-sadshphz.js"></script>
<script type="module" src="/assets/chunk-pmthkscp.js"></script>
<script type="module" src="/assets/chunk-v61ks9f7.js"></script>
<script type="module" src="/assets/entry-tfe07zw7.js"></script>
<script type="module" src="/assets/entry-9ys2gakq.js"></script>
<script type="module" src="/assets/chunk-n1k68xa8.js"></script>
<script type="module" src="/assets/chunk-rsph5pvm.js"></script>
<script type="module" src="/assets/chunk-9t57pdty.js"></script>

View file

@ -14,11 +14,11 @@
href="https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=JetBrains+Mono:wght@400;500;600&display=swap"
/>
<script>
(function(){try{var t=localStorage.getItem("fabro-theme");if(t!=="light"&&t!=="dark")t=window.matchMedia("(prefers-color-scheme:dark)").matches?"dark":"light";document.documentElement.classList.add(t)}catch(e){document.documentElement.classList.add("dark")}})()
(function(){try{var t=localStorage.getItem("fabro-theme");if(t!=="light"&&t!=="dark")t="dark";document.documentElement.classList.add(t)}catch(e){document.documentElement.classList.add("dark")}})()
</script>
{{styles}}
</head>
<body class="h-full">
<body class="h-full font-sans antialiased">
<div id="root"></div>
{{scripts}}
</body>