Add appearance settings for theme, fonts, and syntax highlighting (#1236)
* Add appearance settings for theme, fonts, and syntax highlighting New Appearance section in Settings to configure the app theme, UI and mono font family + size, and a syntax-highlighting theme chosen independently of light/dark. Font family fields default to empty and show the active system stack as the placeholder; a live split-diff preview reflects the choices as they change. The theme picker moves here from General. Preferences are client-only (AsyncStorage) and applied at runtime by patching every registered Unistyles theme via UnistylesRuntime.updateTheme, so existing StyleSheet consumers repaint with no per-component preference reads and no useUnistyles. No daemon/protocol change. * Expand syntax themes and move the preview under the chooser Replace the small fixed syntax-theme set with eight popular themes — GitHub, Catppuccin, Dracula, Tokyo Night, One, Nord, Gruvbox, Solarized — built from a compact per-theme role palette. The app theme and syntax theme stay separate; the only coupling is the light/dark axis: a theme with both variants uses its light palette on a light app and its dark palette on a dark app, while the code frame (gutter, line numbers, background) keeps following the app theme. Default is now GitHub (GitHub keeps its existing hand-tuned maps for an exact match). Move the live preview directly under the syntax-theme chooser, drop the "preview.ts" filename header, and use a clearer code snippet for it. * Update settings e2e for the moved theme picker and Appearance section The theme picker moved from General to the new Appearance section, so the General-content assertion no longer finds "Theme" — point it at "Default send" (which stays in General) and add Appearance to the e2e section map plus an expectAppearanceContent check, and exercise the Appearance section in the sidebar-navigation test. * Render the appearance preview as a unified diff Switch the live preview from side-by-side columns to a single unified diff: unchanged context lines plus "-"/"+" rows for the change, matching how diffs read elsewhere in the app. Same snippet, mono font, syntax colors, and live code-font draft behavior. * Fix code font size change shrinking all UI text applyAppearance scaled the live (already-patched) theme ramp instead of the authored FONT_SIZE ramp, so it was not idempotent: every appearance change re-scaled the current sizes, compounding the whole-UI font scale. With any non-default UI size, changing the code size — or any setting — shrank (or grew) all app text, and it persisted into the theme. Build the ramp from the canonical FONT_SIZE ramp each apply (code size still set absolutely), so applies are idempotent and a code-size change never touches the UI ramp. * Apply the interface font across the app on web react-native-web stamps a default font onto every text element, so the interface font only reached the few components that set a font explicitly. Inject one rule that points all text at a CSS variable (updated live as the setting changes), with high specificity (1,2,0) so it deterministically beats RN-web's base font and Unistyles' generated classes — no stylesheet-order fragility. Code, diff, and monospace surfaces carry a `data-pmono` marker (and their subtrees are excluded), so they keep their code font. Web-only: React Native has no global font cascade, so native still applies the UI font only where components opt in. Inline `code` within chat prose is a known minor gap (its render paths aren't tagged yet). * Keep preview code in the code font * Apply code font settings to terminals * Fix settings host picker import after rebase * Format terminal webview bundle
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { darkHighlightColors, lightHighlightColors } from "../colors.js";
|
||||
import { SYNTAX_THEME_IDS, isSyntaxThemeId, resolveSyntaxColors } from "../themes.js";
|
||||
import type { HighlightStyle } from "../types.js";
|
||||
|
||||
const allStyles: HighlightStyle[] = [
|
||||
"keyword",
|
||||
"comment",
|
||||
"string",
|
||||
"number",
|
||||
"literal",
|
||||
"function",
|
||||
"definition",
|
||||
"class",
|
||||
"type",
|
||||
"tag",
|
||||
"attribute",
|
||||
"property",
|
||||
"variable",
|
||||
"operator",
|
||||
"punctuation",
|
||||
"regexp",
|
||||
"escape",
|
||||
"meta",
|
||||
"heading",
|
||||
"link",
|
||||
];
|
||||
|
||||
const colorSchemes: ("light" | "dark")[] = ["light", "dark"];
|
||||
|
||||
describe("resolveSyntaxColors", () => {
|
||||
for (const id of SYNTAX_THEME_IDS) {
|
||||
for (const colorScheme of colorSchemes) {
|
||||
describe(`${id} (${colorScheme})`, () => {
|
||||
const colors = resolveSyntaxColors(id, colorScheme);
|
||||
|
||||
it("covers all HighlightStyle values", () => {
|
||||
for (const style of allStyles) {
|
||||
expect(colors[style]).toBeDefined();
|
||||
expect(typeof colors[style]).toBe("string");
|
||||
}
|
||||
});
|
||||
|
||||
it("has valid hex color values", () => {
|
||||
for (const style of allStyles) {
|
||||
expect(colors[style]).toMatch(/^#[0-9a-fA-F]{6}$/);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
it("github + light deep-equals lightHighlightColors", () => {
|
||||
expect(resolveSyntaxColors("github", "light")).toEqual(lightHighlightColors);
|
||||
});
|
||||
|
||||
it("github + dark deep-equals darkHighlightColors", () => {
|
||||
expect(resolveSyntaxColors("github", "dark")).toEqual(darkHighlightColors);
|
||||
});
|
||||
|
||||
it("dark-only themes ignore the color scheme", () => {
|
||||
for (const id of ["dracula", "nord"] as const) {
|
||||
expect(resolveSyntaxColors(id, "light")).toEqual(resolveSyntaxColors(id, "dark"));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("isSyntaxThemeId", () => {
|
||||
it("accepts every id in SYNTAX_THEME_IDS", () => {
|
||||
for (const id of SYNTAX_THEME_IDS) {
|
||||
expect(isSyntaxThemeId(id)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects unknown ids", () => {
|
||||
expect(isSyntaxThemeId("auto")).toBe(false);
|
||||
expect(isSyntaxThemeId("github-light")).toBe(false);
|
||||
expect(isSyntaxThemeId("one-dark")).toBe(false);
|
||||
expect(isSyntaxThemeId("nope")).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user