Skip to content

Commit 57ceb98

Browse files
committed
feat(webapp): new Dark theme is the default with a slight contrast bump
Missing or legacy preferences resolve to the pinned Dark theme (not system-resolved, so nobody is surprised by light mode) and the default contrast is 50. Classic remains the flag-off look.
1 parent 33a5415 commit 57ceb98

3 files changed

Lines changed: 17 additions & 12 deletions

File tree

.server-changes/light-theme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ area: webapp
33
type: feature
44
---
55

6-
Adds an opt-in Interface theme setting on the account page behind a feature flag. Choose Classic (the default), System preference (follows your OS), Dark, or Light, and fine-tune the new themes with a contrast slider. System preference switches automatically when your OS appearance changes.
6+
Adds an opt-in Interface theme setting on the account page behind a feature flag. Choose Dark (the new default), System preference (follows your OS), Light, or the original Classic look, and fine-tune the new themes with a contrast slider. System preference switches automatically when your OS appearance changes.

apps/webapp/app/utils/themePreference.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,20 @@ export const ThemePreference = z.enum(["classic", "system", "dark", "light"]);
66
export type ThemePreference = z.infer<typeof ThemePreference>;
77

88
/** Coerce any stored/legacy value into a valid preference. Missing or unknown
9-
* values fall back to `classic` (the default dark theme). */
9+
* values fall back to `dark` - the new dark theme is the default (pinned, not
10+
* system-resolved, so nobody gets surprised by light mode). */
1011
export function normalizeThemePreference(value: unknown): ThemePreference {
1112
const result = ThemePreference.safeParse(value);
12-
return result.success ? result.data : "classic";
13+
return result.success ? result.data : "dark";
1314
}
1415

15-
/** Interface contrast for the System themes, 0 (default) to 100 (max). */
16+
/** The default dark theme ships with a slight contrast bump. */
17+
export const DEFAULT_THEME_CONTRAST = 50;
18+
19+
/** Interface contrast for the System themes, 0 to 100. Missing or invalid
20+
* values fall back to the default bump. */
1621
export function normalizeThemeContrast(value: unknown): number {
1722
const num = typeof value === "string" ? Number(value) : value;
18-
if (typeof num !== "number" || !Number.isFinite(num)) return 0;
23+
if (typeof num !== "number" || !Number.isFinite(num)) return DEFAULT_THEME_CONTRAST;
1924
return Math.min(100, Math.max(0, Math.round(num)));
2025
}

apps/webapp/test/themePreference.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ describe("normalizeThemePreference", () => {
1111
}
1212
});
1313

14-
it("falls back to classic for legacy/unknown values", () => {
15-
expect(normalizeThemePreference("solarized")).toBe("classic");
16-
expect(normalizeThemePreference("")).toBe("classic");
17-
expect(normalizeThemePreference(42)).toBe("classic");
18-
expect(normalizeThemePreference(null)).toBe("classic");
14+
it("falls back to dark for legacy/unknown values", () => {
15+
expect(normalizeThemePreference("solarized")).toBe("dark");
16+
expect(normalizeThemePreference("")).toBe("dark");
17+
expect(normalizeThemePreference(42)).toBe("dark");
18+
expect(normalizeThemePreference(null)).toBe("dark");
1919
});
2020

21-
it("falls back to classic for undefined", () => {
22-
expect(normalizeThemePreference(undefined)).toBe("classic");
21+
it("falls back to dark for undefined", () => {
22+
expect(normalizeThemePreference(undefined)).toBe("dark");
2323
});
2424
});
2525

0 commit comments

Comments
 (0)