feat(ui): default theme to system (follow OS light/dark)
Light has been signed off, so the theme store's default choice is now `system` (it was `dark` during the incremental migration). This matches the app.html pre-paint guard, which already resolved an unset choice via prefers-color-scheme — removing the brief boot-time mismatch where the store re-pinned dark. Users still pin light/dark via the account-menu picker. Updates the store default + its test and the design-system / finalize-plan docs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+3
-3
@@ -25,9 +25,9 @@ Merged to `development` via PR #26 (2026-05-22): a shared design-token
|
|||||||
system (`ui/frontend/src/lib/theme/`), light/dark theming with a picker
|
system (`ui/frontend/src/lib/theme/`), light/dark theming with a picker
|
||||||
and pre-paint guard, and the whole UI migrated onto the tokens.
|
and pre-paint guard, and the whole UI migrated onto the tokens.
|
||||||
Documented literal exceptions: the battle-scene data-viz palette, overlay
|
Documented literal exceptions: the battle-scene data-viz palette, overlay
|
||||||
scrims, and directional/deliberate drop shadows. The default theme is
|
scrims, and directional/deliberate drop shadows. The default theme
|
||||||
dark; flipping it to `system` is an available follow-up. Tokens and
|
follows the OS (`system`); the account-menu picker pins light or dark.
|
||||||
conventions live in `ui/docs/design-system.md`.
|
Tokens and conventions live in `ui/docs/design-system.md`.
|
||||||
|
|
||||||
Goal: replace the ad-hoc per-component styling (inline hex colors like
|
Goal: replace the ad-hoc per-component styling (inline hex colors like
|
||||||
`#0a0e1a`, one-off spacing) with a shared design language so every view
|
`#0a0e1a`, one-off spacing) with a shared design language so every view
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ the colour block in `tokens.css`.
|
|||||||
persists the choice, applies `data-theme`, and — while the choice is
|
persists the choice, applies `data-theme`, and — while the choice is
|
||||||
`system` — follows OS theme changes via `matchMedia`.
|
`system` — follows OS theme changes via `matchMedia`.
|
||||||
- The account menu (`account-menu.svelte`) exposes the picker. The
|
- The account menu (`account-menu.svelte`) exposes the picker. The
|
||||||
default is `dark`; `system` follows the OS.
|
default is `system` (it follows the OS preference); `light` / `dark`
|
||||||
|
pin a theme.
|
||||||
|
|
||||||
The `app.html` guard and the store deliberately duplicate the
|
The `app.html` guard and the store deliberately duplicate the
|
||||||
resolution logic (one runs before modules load, the other after) — keep
|
resolution logic (one runs before modules load, the other after) — keep
|
||||||
@@ -115,6 +116,5 @@ The only remaining literal colours are the documented exceptions above:
|
|||||||
the battle-scene data-viz palette, the overlay scrims, and the
|
the battle-scene data-viz palette, the overlay scrims, and the
|
||||||
directional / deliberate drop shadows.
|
directional / deliberate drop shadows.
|
||||||
|
|
||||||
The default theme is **dark** while light coherence is being verified
|
The default theme is **`system`** — it follows the OS light/dark
|
||||||
across the migrated views; once the owner signs off on light, the
|
preference; users can pin light or dark via the account-menu picker.
|
||||||
default can flip to `system`.
|
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ export const THEME_STORAGE_KEY = "galaxy-theme";
|
|||||||
const SYSTEM_LIGHT_QUERY = "(prefers-color-scheme: light)";
|
const SYSTEM_LIGHT_QUERY = "(prefers-color-scheme: light)";
|
||||||
|
|
||||||
function readStoredChoice(): ThemeChoice {
|
function readStoredChoice(): ThemeChoice {
|
||||||
if (typeof localStorage === "undefined") return "dark";
|
if (typeof localStorage === "undefined") return "system";
|
||||||
const value = localStorage.getItem(THEME_STORAGE_KEY);
|
const value = localStorage.getItem(THEME_STORAGE_KEY);
|
||||||
return value === "light" || value === "dark" || value === "system"
|
return value === "light" || value === "dark" || value === "system"
|
||||||
? value
|
? value
|
||||||
: "dark";
|
: "system";
|
||||||
}
|
}
|
||||||
|
|
||||||
function systemTheme(): ResolvedTheme {
|
function systemTheme(): ResolvedTheme {
|
||||||
|
|||||||
@@ -42,9 +42,10 @@ describe("theme store", () => {
|
|||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("defaults to dark and applies it to the document", async () => {
|
it("defaults to system and applies the resolved theme", async () => {
|
||||||
const { theme } = await freshStore();
|
const { theme } = await freshStore();
|
||||||
expect(theme.choice).toBe("dark");
|
expect(theme.choice).toBe("system");
|
||||||
|
// freshStore() leaves the OS at dark (prefersLight = false).
|
||||||
expect(theme.resolved).toBe("dark");
|
expect(theme.resolved).toBe("dark");
|
||||||
expect(document.documentElement.dataset.theme).toBe("dark");
|
expect(document.documentElement.dataset.theme).toBe("dark");
|
||||||
});
|
});
|
||||||
@@ -80,9 +81,9 @@ describe("theme store", () => {
|
|||||||
expect(document.documentElement.dataset.theme).toBe("light");
|
expect(document.documentElement.dataset.theme).toBe("light");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("falls back to dark for an unrecognised stored value", async () => {
|
it("falls back to system for an unrecognised stored value", async () => {
|
||||||
localStorage.setItem(STORAGE_KEY, "neon");
|
localStorage.setItem(STORAGE_KEY, "neon");
|
||||||
const { theme } = await freshStore();
|
const { theme } = await freshStore();
|
||||||
expect(theme.choice).toBe("dark");
|
expect(theme.choice).toBe("system");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user