feat(app): align interface typography to base size
Treat the appearance setting as the rendered base size so the type ramp scales predictably. Native starts one pixel larger while web and desktop preserve the authored 14px base, with legacy values migrated on load.
This commit is contained in:
@@ -2385,12 +2385,12 @@ const styles = StyleSheet.create((theme) => ({
|
||||
paddingRight: theme.spacing[4],
|
||||
},
|
||||
composerTitle: {
|
||||
fontSize: theme.fontSize.xl,
|
||||
fontSize: theme.fontSize["2xl"],
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
errorText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
color: theme.colors.destructive,
|
||||
lineHeight: 20,
|
||||
},
|
||||
@@ -2453,16 +2453,16 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
badgeText: {
|
||||
minWidth: 0,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
color: theme.colors.foregroundMuted,
|
||||
flexShrink: 1,
|
||||
},
|
||||
tooltipText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
color: theme.colors.popoverForeground,
|
||||
},
|
||||
refDivergenceLabel: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontVariant: ["tabular-nums"],
|
||||
},
|
||||
|
||||
@@ -250,7 +250,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
tileDescription: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
lineHeight: 18,
|
||||
},
|
||||
communityRow: {
|
||||
|
||||
@@ -1131,7 +1131,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
noTargetText: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
body: {
|
||||
padding: theme.spacing[4],
|
||||
@@ -1153,7 +1153,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
projectTitle: {
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.lg,
|
||||
fontSize: theme.fontSize.base,
|
||||
fontWeight: theme.fontWeight.medium,
|
||||
flexShrink: 1,
|
||||
},
|
||||
@@ -1161,7 +1161,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
padding: theme.spacing[1],
|
||||
},
|
||||
titleIconFallbackText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
fontWeight: theme.fontWeight.medium,
|
||||
},
|
||||
iconColor: {
|
||||
@@ -1178,7 +1178,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
emptyScripts: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
scriptRow: {
|
||||
flexDirection: "row",
|
||||
@@ -1217,11 +1217,11 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
modalLabel: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
modalInput: {
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
paddingVertical: theme.spacing[2],
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
borderRadius: theme.borderRadius.md,
|
||||
@@ -1231,7 +1231,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
modalMultilineInput: {
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
paddingVertical: theme.spacing[2],
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
borderRadius: theme.borderRadius.md,
|
||||
@@ -1249,7 +1249,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
fieldError: {
|
||||
color: theme.colors.palette.red[300],
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
serviceToggleRow: {
|
||||
flexDirection: "row",
|
||||
@@ -1263,12 +1263,12 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
serviceToggleLabel: {
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
fontWeight: theme.fontWeight.medium,
|
||||
},
|
||||
modalHint: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
placeholderColor: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
|
||||
@@ -180,7 +180,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
emptyText: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
errorsBanner: {
|
||||
borderWidth: 1,
|
||||
@@ -192,7 +192,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
errorsBannerText: {
|
||||
color: theme.colors.palette.red[300],
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
row: {
|
||||
gap: theme.spacing[3],
|
||||
@@ -217,7 +217,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
justifyContent: "center",
|
||||
},
|
||||
iconFallbackText: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
spinnerColor: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
|
||||
@@ -408,7 +408,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
errorsBannerText: {
|
||||
color: theme.colors.palette.red[300],
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
filterEmpty: {
|
||||
flexGrow: 1,
|
||||
@@ -439,12 +439,12 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
emptyDescription: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
textAlign: "center",
|
||||
},
|
||||
message: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.lg,
|
||||
fontSize: theme.fontSize.base,
|
||||
textAlign: "center",
|
||||
},
|
||||
// Static color holder read by the spinner; keeps the muted token without
|
||||
|
||||
@@ -244,7 +244,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
emptyText: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.lg,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
loadingContainer: {
|
||||
flex: 1,
|
||||
@@ -257,7 +257,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
footerHint: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
errorsBannerWrap: {
|
||||
paddingHorizontal: {
|
||||
@@ -275,6 +275,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
errorsBannerText: {
|
||||
color: theme.colors.palette.red[300],
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1582,7 +1582,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
loadingText: {
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.lg,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
@@ -1600,14 +1600,14 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
aboutValue: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
aboutVersionMismatch: {
|
||||
color: theme.colors.palette.amber[500],
|
||||
},
|
||||
aboutErrorText: {
|
||||
color: theme.colors.palette.red[300],
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
marginTop: theme.spacing[1],
|
||||
},
|
||||
aboutCommunity: {
|
||||
@@ -1630,7 +1630,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
themeTriggerText: {
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
terminalScrollbackInput: {
|
||||
width: 112,
|
||||
@@ -1642,7 +1642,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
borderColor: theme.colors.border,
|
||||
backgroundColor: theme.colors.surface2,
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
textAlign: "right",
|
||||
},
|
||||
placeholder: {
|
||||
@@ -1653,7 +1653,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
placeholderText: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1693,7 +1693,7 @@ const sidebarStyles = StyleSheet.create((theme) => ({
|
||||
gap: theme.spacing[1],
|
||||
},
|
||||
groupLabel: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
fontWeight: theme.fontWeight.medium,
|
||||
color: theme.colors.foregroundMuted,
|
||||
paddingHorizontal: theme.spacing[2],
|
||||
|
||||
@@ -20,9 +20,9 @@ import { Switch } from "@/components/ui/switch";
|
||||
import { SettingsSection } from "@/screens/settings/settings-section";
|
||||
import {
|
||||
MAX_CODE_FONT_SIZE,
|
||||
MAX_UI_FONT_SIZE,
|
||||
MAX_UI_BASE_FONT_SIZE,
|
||||
MIN_CODE_FONT_SIZE,
|
||||
MIN_UI_FONT_SIZE,
|
||||
MIN_UI_BASE_FONT_SIZE,
|
||||
parseClampedFontSize,
|
||||
sanitizeFontFamily,
|
||||
useAppSettings,
|
||||
@@ -470,13 +470,13 @@ export function AppearanceSection() {
|
||||
|
||||
const [uiFontDraft, setUiFontDraft] = useState(settings.uiFontFamily);
|
||||
const [monoFontDraft, setMonoFontDraft] = useState(settings.monoFontFamily);
|
||||
const [uiSizeDraft, setUiSizeDraft] = useState(String(settings.uiFontSize));
|
||||
const [uiBaseSizeDraft, setUiBaseSizeDraft] = useState(String(settings.uiBaseFontSize));
|
||||
const [codeSizeDraft, setCodeSizeDraft] = useState(String(settings.codeFontSize));
|
||||
|
||||
// Resync numeric drafts when the committed value changes elsewhere.
|
||||
useEffect(() => {
|
||||
setUiSizeDraft(String(settings.uiFontSize));
|
||||
}, [settings.uiFontSize]);
|
||||
setUiBaseSizeDraft(String(settings.uiBaseFontSize));
|
||||
}, [settings.uiBaseFontSize]);
|
||||
useEffect(() => {
|
||||
setCodeSizeDraft(String(settings.codeFontSize));
|
||||
}, [settings.codeFontSize]);
|
||||
@@ -546,25 +546,25 @@ export function AppearanceSection() {
|
||||
[settings.monoFontFamily, updateSettings],
|
||||
);
|
||||
|
||||
const handleUiSizeChange = useCallback((value: string) => {
|
||||
setUiSizeDraft(value.replace(/[^\d]/g, ""));
|
||||
const handleUiBaseSizeChange = useCallback((value: string) => {
|
||||
setUiBaseSizeDraft(value.replace(/[^\d]/g, ""));
|
||||
}, []);
|
||||
|
||||
const handleCodeSizeChange = useCallback((value: string) => {
|
||||
setCodeSizeDraft(value.replace(/[^\d]/g, ""));
|
||||
}, []);
|
||||
|
||||
const commitUiSize = useCallback(() => {
|
||||
const parsed = parseClampedFontSize(uiSizeDraft, {
|
||||
min: MIN_UI_FONT_SIZE,
|
||||
max: MAX_UI_FONT_SIZE,
|
||||
const commitUiBaseSize = useCallback(() => {
|
||||
const parsed = parseClampedFontSize(uiBaseSizeDraft, {
|
||||
min: MIN_UI_BASE_FONT_SIZE,
|
||||
max: MAX_UI_BASE_FONT_SIZE,
|
||||
});
|
||||
const next = parsed ?? settings.uiFontSize;
|
||||
setUiSizeDraft(String(next));
|
||||
if (next !== settings.uiFontSize) {
|
||||
void updateSettings({ uiFontSize: next });
|
||||
const next = parsed ?? settings.uiBaseFontSize;
|
||||
setUiBaseSizeDraft(String(next));
|
||||
if (next !== settings.uiBaseFontSize) {
|
||||
void updateSettings({ uiBaseFontSize: next });
|
||||
}
|
||||
}, [settings.uiFontSize, uiSizeDraft, updateSettings]);
|
||||
}, [settings.uiBaseFontSize, uiBaseSizeDraft, updateSettings]);
|
||||
|
||||
const commitCodeSize = useCallback(() => {
|
||||
const parsed = parseClampedFontSize(codeSizeDraft, {
|
||||
@@ -630,12 +630,12 @@ export function AppearanceSection() {
|
||||
/>
|
||||
) : null}
|
||||
<FontSizeRow
|
||||
title={t("settings.appearance.fonts.interfaceSize")}
|
||||
accessibilityLabel={t("settings.appearance.fonts.interfaceSizeAccessibility")}
|
||||
draft={uiSizeDraft}
|
||||
title={t("settings.appearance.fonts.baseSize")}
|
||||
accessibilityLabel={t("settings.appearance.fonts.baseSizeAccessibility")}
|
||||
draft={uiBaseSizeDraft}
|
||||
withBorder={showInterfaceFontFamilyRow}
|
||||
onChangeDraft={handleUiSizeChange}
|
||||
onCommit={commitUiSize}
|
||||
onChangeDraft={handleUiBaseSizeChange}
|
||||
onCommit={commitUiBaseSize}
|
||||
/>
|
||||
<FontFamilyRow
|
||||
title={t("settings.appearance.fonts.codeFont")}
|
||||
@@ -697,7 +697,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
triggerText: {
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
swatch: {
|
||||
width: ICON_SIZE.md,
|
||||
@@ -718,7 +718,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
borderColor: theme.colors.border,
|
||||
backgroundColor: theme.colors.surface2,
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
textAlign: "left",
|
||||
},
|
||||
sizeField: {
|
||||
@@ -736,12 +736,12 @@ const styles = StyleSheet.create((theme) => ({
|
||||
borderColor: theme.colors.border,
|
||||
backgroundColor: theme.colors.surface2,
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
textAlign: "right",
|
||||
},
|
||||
unit: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
placeholderColor: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
|
||||
@@ -26,7 +26,6 @@ interface FakeTheme {
|
||||
colorScheme: "light" | "dark";
|
||||
fontFamily: { ui: string; mono: string };
|
||||
fontSize: {
|
||||
xs: number;
|
||||
code: number;
|
||||
sm: number;
|
||||
base: number;
|
||||
@@ -45,15 +44,14 @@ function makeFakeTheme(): FakeTheme {
|
||||
colorScheme: "dark",
|
||||
fontFamily: { ui: "seed-ui-stack", mono: "seed-mono-stack" },
|
||||
fontSize: {
|
||||
xs: 12,
|
||||
code: 12,
|
||||
sm: 14,
|
||||
base: 16,
|
||||
lg: 18,
|
||||
xl: 20,
|
||||
"2xl": 22,
|
||||
"3xl": 26,
|
||||
"4xl": 34,
|
||||
sm: 12,
|
||||
base: 14,
|
||||
lg: 16,
|
||||
xl: 18,
|
||||
"2xl": 20,
|
||||
"3xl": 22,
|
||||
"4xl": 26,
|
||||
},
|
||||
lineHeight: { diff: 22 },
|
||||
colors: { foreground: "#fff", syntax: {} },
|
||||
@@ -64,7 +62,7 @@ function makeInput(overrides: Partial<AppearanceInput> = {}): AppearanceInput {
|
||||
return {
|
||||
uiFontFamily: "",
|
||||
monoFontFamily: "",
|
||||
uiFontSize: 16,
|
||||
uiBaseFontSize: 14,
|
||||
codeFontSize: 12,
|
||||
syntaxTheme: "one",
|
||||
...overrides,
|
||||
@@ -93,7 +91,7 @@ describe("applyAppearance", () => {
|
||||
it("patches the active theme before inactive registry entries", () => {
|
||||
runtime.themeName = "darkPureBlack";
|
||||
|
||||
applyAppearance(makeInput({ uiFontSize: 17 }));
|
||||
applyAppearance(makeInput({ uiBaseFontSize: 15 }));
|
||||
|
||||
expect(updateTheme.mock.calls.map((call) => call[0])).toEqual([
|
||||
"darkPureBlack",
|
||||
@@ -114,25 +112,24 @@ describe("applyAppearance", () => {
|
||||
});
|
||||
|
||||
it("scales the whole UI ramp proportionally while preserving ratios", () => {
|
||||
applyAppearance(makeInput({ uiFontSize: 14 }));
|
||||
applyAppearance(makeInput({ uiBaseFontSize: 15 }));
|
||||
|
||||
const { fontSize } = runCapturedUpdater();
|
||||
// r = 14 / 16 = 0.875
|
||||
expect(fontSize.base).toBe(14); // round(16 * 0.875)
|
||||
expect(fontSize.lg).toBe(16); // round(18 * 0.875) = round(15.75)
|
||||
expect(fontSize.xs).toBe(11); // round(12 * 0.875) = round(10.5)
|
||||
expect(fontSize["4xl"]).toBe(30); // round(34 * 0.875) = round(29.75)
|
||||
expect(fontSize.base).toBe(15);
|
||||
expect(fontSize.sm).toBe(13);
|
||||
expect(fontSize.lg).toBe(17);
|
||||
expect(fontSize.xl).toBe(19);
|
||||
expect(fontSize["4xl"]).toBe(28);
|
||||
});
|
||||
|
||||
it("derives the UI ramp from the canonical sizes, not the live theme (no compounding)", () => {
|
||||
applyAppearance(makeInput({ uiFontSize: 14 }));
|
||||
applyAppearance(makeInput({ uiBaseFontSize: 15 }));
|
||||
|
||||
// Simulate a theme whose fontSize was already scaled by a prior apply; the
|
||||
// updater must ignore it and rebuild from the authored FONT_SIZE ramp.
|
||||
const updater = updateTheme.mock.calls[0]?.[1] as unknown as ThemeUpdater;
|
||||
const alreadyScaled = makeFakeTheme();
|
||||
alreadyScaled.fontSize = {
|
||||
xs: 4,
|
||||
code: 4,
|
||||
sm: 4,
|
||||
base: 4,
|
||||
@@ -144,21 +141,22 @@ describe("applyAppearance", () => {
|
||||
};
|
||||
|
||||
const { fontSize } = updater(alreadyScaled);
|
||||
expect(fontSize.base).toBe(14); // not 4 * 0.875 — rebuilt from FONT_SIZE
|
||||
expect(fontSize.lg).toBe(16);
|
||||
expect(fontSize.base).toBe(15); // rebuilt from FONT_SIZE, not the live value of 4
|
||||
expect(fontSize.lg).toBe(17);
|
||||
});
|
||||
|
||||
it("leaves the UI ramp at authored sizes when only the code size changes", () => {
|
||||
applyAppearance(makeInput({ uiFontSize: 16, codeFontSize: 10 }));
|
||||
applyAppearance(makeInput({ uiBaseFontSize: 14, codeFontSize: 10 }));
|
||||
|
||||
const { fontSize } = runCapturedUpdater();
|
||||
expect(fontSize.base).toBe(16);
|
||||
expect(fontSize.sm).toBe(14);
|
||||
expect(fontSize.base).toBe(14);
|
||||
expect(fontSize.lg).toBe(16);
|
||||
expect(fontSize.sm).toBe(12);
|
||||
expect(fontSize.code).toBe(10);
|
||||
});
|
||||
|
||||
it("sets fontSize.code to codeFontSize regardless of the UI font size", () => {
|
||||
applyAppearance(makeInput({ uiFontSize: 14, codeFontSize: 18 }));
|
||||
applyAppearance(makeInput({ uiBaseFontSize: 14, codeFontSize: 18 }));
|
||||
|
||||
expect(runCapturedUpdater().fontSize.code).toBe(18);
|
||||
});
|
||||
|
||||
@@ -11,30 +11,26 @@ import { applyRootUiFont } from "./apply-root-font";
|
||||
|
||||
const ALL_THEME_KEYS = Object.keys(REGISTERED_THEMES) as (keyof typeof REGISTERED_THEMES)[];
|
||||
|
||||
// The UI font size at which the FONT_SIZE ramp is authored (1.0 scale factor).
|
||||
const BASE_UI_REFERENCE = FONT_SIZE.base; // 16
|
||||
|
||||
export interface AppearanceInput {
|
||||
uiFontFamily: string; // "" -> default stack
|
||||
monoFontFamily: string; // "" -> default stack
|
||||
uiFontSize: number; // already clamped
|
||||
uiBaseFontSize: number; // already clamped
|
||||
codeFontSize: number; // already clamped
|
||||
syntaxTheme: SyntaxThemeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the font-size ramp from the canonical `FONT_SIZE` ramp, scaled
|
||||
* proportionally by `uiSize / 16` so the type hierarchy is preserved at non-default
|
||||
* proportionally from the requested base size so the type hierarchy is preserved
|
||||
* sizes. Deriving from the authored ramp — NOT the live (possibly already-scaled)
|
||||
* theme — makes `applyAppearance` idempotent: repeated applies never compound, and a
|
||||
* code-size change (uiSize unchanged) leaves the UI ramp at its authored values.
|
||||
* code-size change (base size unchanged) leaves the UI ramp at its authored values.
|
||||
* `code` is set absolutely to `codeSize`, never scaled by the UI factor — a separate
|
||||
* control on a separate semantic axis (mono/diff text).
|
||||
*/
|
||||
function scaleFontSize(uiSize: number, codeSize: number): Theme["fontSize"] {
|
||||
const r = uiSize / BASE_UI_REFERENCE;
|
||||
function scaleFontSize(uiBaseSize: number, codeSize: number): Theme["fontSize"] {
|
||||
const r = uiBaseSize / FONT_SIZE.base;
|
||||
return {
|
||||
xs: Math.round(FONT_SIZE.xs * r),
|
||||
sm: Math.round(FONT_SIZE.sm * r),
|
||||
base: Math.round(FONT_SIZE.base * r),
|
||||
lg: Math.round(FONT_SIZE.lg * r),
|
||||
@@ -72,7 +68,7 @@ export function applyAppearance(input: AppearanceInput): void {
|
||||
for (const key of themeKeys) {
|
||||
UnistylesRuntime.updateTheme(key, (t) => {
|
||||
const fontFamily = { ui, mono };
|
||||
const fontSize = scaleFontSize(input.uiFontSize, input.codeFontSize);
|
||||
const fontSize = scaleFontSize(input.uiBaseFontSize, input.codeFontSize);
|
||||
const lineHeight = { ...t.lineHeight, diff: diffLineHeight };
|
||||
if (t.colorScheme === "light") {
|
||||
return {
|
||||
|
||||
@@ -327,7 +327,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
triggerText: {
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
swatch: {
|
||||
width: ICON_SIZE.md,
|
||||
@@ -348,7 +348,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
nameText: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
flexShrink: 1,
|
||||
},
|
||||
preview: {
|
||||
@@ -363,7 +363,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
previewTitle: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
flexShrink: 1,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1793,7 +1793,7 @@ const terminalProfileStyles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
emptyText: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
textAlign: "center",
|
||||
},
|
||||
}));
|
||||
@@ -1840,23 +1840,23 @@ const styles = StyleSheet.create((theme) => ({
|
||||
maxWidth: 200,
|
||||
},
|
||||
badgeText: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
color: theme.colors.foregroundMuted,
|
||||
flexShrink: 1,
|
||||
},
|
||||
errorText: {
|
||||
color: theme.colors.palette.red[300],
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
marginBottom: theme.spacing[2],
|
||||
},
|
||||
connectionLatency: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
marginRight: theme.spacing[2],
|
||||
},
|
||||
confirmText: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
confirmActions: {
|
||||
flexDirection: "row",
|
||||
@@ -1875,7 +1875,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
emptyText: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -505,7 +505,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
rowLabel: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
color: theme.colors.foreground,
|
||||
flexShrink: 1,
|
||||
},
|
||||
@@ -535,11 +535,11 @@ const styles = StyleSheet.create((theme) => ({
|
||||
backgroundColor: theme.colors.surface3,
|
||||
},
|
||||
capturingText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
unassignedText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
separator: {
|
||||
@@ -550,7 +550,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
padding: theme.spacing[4],
|
||||
},
|
||||
mobileText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -166,8 +166,8 @@ export function MetadataGenerationPage({ serverId }: { serverId: string }) {
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
description: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
lineHeight: theme.fontSize.xs * 1.45,
|
||||
fontSize: theme.fontSize.sm,
|
||||
lineHeight: theme.fontSize.sm * 1.45,
|
||||
marginHorizontal: theme.spacing[1],
|
||||
},
|
||||
loading: {
|
||||
|
||||
@@ -451,11 +451,11 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
pluginTitle: { flexDirection: "row", alignItems: "center", gap: theme.spacing[2] },
|
||||
actions: { flexDirection: "row", flexWrap: "wrap", gap: theme.spacing[2] },
|
||||
error: { color: theme.colors.statusDanger, fontSize: theme.fontSize.xs },
|
||||
error: { color: theme.colors.statusDanger, fontSize: theme.fontSize.sm },
|
||||
empty: { padding: theme.spacing[4], alignItems: "center" },
|
||||
logsState: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
textAlign: "center",
|
||||
paddingVertical: theme.spacing[6],
|
||||
},
|
||||
@@ -469,16 +469,16 @@ const styles = StyleSheet.create((theme) => ({
|
||||
logStdout: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontFamily: theme.fontFamily.mono,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
logStderr: {
|
||||
color: theme.colors.statusDanger,
|
||||
fontFamily: theme.fontFamily.mono,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
logMessage: {
|
||||
color: theme.colors.foreground,
|
||||
fontFamily: theme.fontFamily.mono,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -483,7 +483,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
emptyText: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
row: {
|
||||
gap: theme.spacing[3],
|
||||
@@ -522,15 +522,15 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
statusLabel: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
separator: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
errorText: {
|
||||
color: theme.colors.palette.red[300],
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
marginTop: theme.spacing[1],
|
||||
},
|
||||
trailingControls: {
|
||||
|
||||
@@ -89,8 +89,8 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
tooltipText: {
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
maxWidth: 280,
|
||||
lineHeight: theme.fontSize.sm * 1.4,
|
||||
lineHeight: theme.fontSize.base * 1.4,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -254,6 +254,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
submitError: {
|
||||
color: theme.colors.palette.red[300],
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -223,7 +223,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
title: {
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize["3xl"],
|
||||
fontSize: theme.fontSize.base,
|
||||
fontWeight: theme.fontWeight.semibold,
|
||||
textAlign: "left",
|
||||
},
|
||||
@@ -240,7 +240,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
logsMeta: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
logsContainer: {
|
||||
height: 200,
|
||||
|
||||
@@ -1928,7 +1928,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
flexShrink: 1,
|
||||
minWidth: 0,
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
userSelect: "none",
|
||||
},
|
||||
@@ -2013,7 +2013,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
newTabTooltipText: {
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
newTabTooltipRow: {
|
||||
flexDirection: "row",
|
||||
@@ -2028,10 +2028,10 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
tooltipAgentId: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
menuItemHint: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -312,8 +312,8 @@ const styles = StyleSheet.create((theme) => ({
|
||||
opacity: 0.6,
|
||||
},
|
||||
splitButtonText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
lineHeight: theme.fontSize.sm * 1.5,
|
||||
fontSize: theme.fontSize.base,
|
||||
lineHeight: theme.fontSize.base * 1.5,
|
||||
color: theme.colors.foreground,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
},
|
||||
@@ -322,7 +322,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: theme.spacing[2],
|
||||
minHeight: theme.fontSize.sm * 1.5,
|
||||
minHeight: theme.fontSize.base * 1.5,
|
||||
},
|
||||
splitButtonSpinnerOnly: {
|
||||
transform: [{ scale: 0.8 }],
|
||||
|
||||
@@ -300,18 +300,18 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
description: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
textAlign: "center",
|
||||
},
|
||||
error: {
|
||||
color: theme.colors.destructive,
|
||||
fontSize: theme.fontSize.sm,
|
||||
lineHeight: Math.round(theme.fontSize.sm * 1.4),
|
||||
fontSize: theme.fontSize.base,
|
||||
lineHeight: Math.round(theme.fontSize.base * 1.4),
|
||||
textAlign: "center",
|
||||
},
|
||||
errorTooltip: {
|
||||
color: theme.colors.popoverForeground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
maxWidth: 420,
|
||||
},
|
||||
actions: {
|
||||
|
||||
@@ -1249,9 +1249,7 @@ function WorkspaceHeaderTitleBar({
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.headerTitleTextGroup}>
|
||||
<ScreenTitle testID="workspace-header-title" style={styles.headerTitle}>
|
||||
{title}
|
||||
</ScreenTitle>
|
||||
<ScreenTitle testID="workspace-header-title">{title}</ScreenTitle>
|
||||
<WorkspaceHeaderProjectRow
|
||||
subtitle={subtitle}
|
||||
isSubtitleDistinct={isSubtitleDistinct}
|
||||
@@ -3932,9 +3930,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
headerTitleContainer: {
|
||||
flex: 1,
|
||||
flexShrink: 1,
|
||||
@@ -3982,15 +3977,15 @@ const styles = StyleSheet.create((theme) => ({
|
||||
headerProjectTitle: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: {
|
||||
xs: theme.fontSize.xs,
|
||||
md: theme.fontSize.sm,
|
||||
xs: theme.fontSize.sm,
|
||||
md: theme.fontSize.base,
|
||||
},
|
||||
flexShrink: 1,
|
||||
minWidth: 0,
|
||||
},
|
||||
headerProjectSeparator: {
|
||||
color: theme.colors.foregroundExtraMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
flexShrink: 0,
|
||||
},
|
||||
headerTitleSkeleton: {
|
||||
@@ -4055,7 +4050,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
newTabTooltipText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
color: theme.colors.popoverForeground,
|
||||
},
|
||||
newTabTooltipRow: {
|
||||
@@ -4093,7 +4088,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
minWidth: 0,
|
||||
flex: 1,
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
headerMenuProfileIconWrapper: {
|
||||
width: 16,
|
||||
@@ -4157,7 +4152,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
flexShrink: 1,
|
||||
minWidth: 0,
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
},
|
||||
tabLabelWithCloseButton: {
|
||||
|
||||
@@ -774,8 +774,8 @@ const styles = StyleSheet.create((theme) => ({
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
splitButtonText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
lineHeight: theme.fontSize.sm * 1.5,
|
||||
fontSize: theme.fontSize.base,
|
||||
lineHeight: theme.fontSize.base * 1.5,
|
||||
color: theme.colors.foreground,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
},
|
||||
@@ -784,7 +784,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: theme.spacing[1.5],
|
||||
minHeight: theme.fontSize.sm * 1.5,
|
||||
minHeight: theme.fontSize.base * 1.5,
|
||||
},
|
||||
scriptList: {
|
||||
paddingVertical: theme.spacing[1],
|
||||
@@ -803,7 +803,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
flexShrink: 0,
|
||||
},
|
||||
scriptName: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
lineHeight: 18,
|
||||
flexShrink: 1,
|
||||
@@ -838,7 +838,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
hostLabel: {
|
||||
flexShrink: 1,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
lineHeight: 14,
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
@@ -881,7 +881,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
minWidth: 0,
|
||||
},
|
||||
tooltipText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
color: theme.colors.popoverForeground,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -332,7 +332,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
flexShrink: 1,
|
||||
},
|
||||
optionLabel: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontSize: theme.fontSize.base,
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
optionTrailingSlot: {
|
||||
|
||||
@@ -137,6 +137,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
menuItemHint: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user