diff --git a/apps/mobile/src/components/ComposerEditor.ios.test.tsx b/apps/mobile/src/components/ComposerEditor.ios.test.tsx index f7ce1f9918d85..029f454b902f7 100644 --- a/apps/mobile/src/components/ComposerEditor.ios.test.tsx +++ b/apps/mobile/src/components/ComposerEditor.ios.test.tsx @@ -128,7 +128,7 @@ vi.mock("../lib/useUniwindTheme", () => ({ useUniwindTheme: () => ({}) })); vi.mock("../lib/nativeComposerTheme", () => ({ createNativeComposerTheme: () => ({}) })); vi.mock("../lib/contextChipIconSource", () => ({ contextChipIconUri: () => "" })); vi.mock("../features/settings/appearance/useScaledTextRole", () => ({ - useScaledTextRole: () => ({ fontSize: 16, lineHeight: 22 }), + useComposerTextRole: () => ({ fontSize: 16, lineHeight: 22 }), })); vi.mock("../lib/uuid", () => ({ uuidv4: () => "uuid", randomHex: () => "0000" })); vi.mock("../state/assets", () => ({ assetEnvironment: {} })); diff --git a/apps/mobile/src/features/settings/appearance/useScaledTextRole.ts b/apps/mobile/src/features/settings/appearance/useScaledTextRole.ts index ffeb6114c17a5..c71298a89822a 100644 --- a/apps/mobile/src/features/settings/appearance/useScaledTextRole.ts +++ b/apps/mobile/src/features/settings/appearance/useScaledTextRole.ts @@ -1,4 +1,6 @@ -import { resolveScaledTextRole } from "../../../lib/appearancePreferences"; +import { Platform, useWindowDimensions } from "react-native"; + +import { resolveComposerTextRole, resolveScaledTextRole } from "../../../lib/appearancePreferences"; import { MOBILE_TYPOGRAPHY } from "../../../lib/typography"; import { useAppearancePreferences } from "./AppearancePreferencesProvider"; @@ -12,3 +14,9 @@ export function useScaledTextRole(role: keyof typeof MOBILE_TYPOGRAPHY): ScaledT const { appearance } = useAppearancePreferences(); return resolveScaledTextRole(role, appearance.baseFontSize); } + +export function useComposerTextRole(): ScaledTextRole { + const body = useScaledTextRole("body"); + const { fontScale } = useWindowDimensions(); + return resolveComposerTextRole(body, fontScale, Platform.OS); +} diff --git a/apps/mobile/src/features/threads/ThreadComposer.tsx b/apps/mobile/src/features/threads/ThreadComposer.tsx index fc845138bc22a..33d7ca70e5b2b 100644 --- a/apps/mobile/src/features/threads/ThreadComposer.tsx +++ b/apps/mobile/src/features/threads/ThreadComposer.tsx @@ -91,7 +91,7 @@ import { isModelSelectionUnavailable, serverConfigForCwd, } from "../../lib/modelOptions"; -import { useScaledTextRole, type ScaledTextRole } from "../settings/appearance/useScaledTextRole"; +import { useComposerTextRole, type ScaledTextRole } from "../settings/appearance/useScaledTextRole"; import type { RemoteClientConnectionState } from "../../lib/connection"; import { MOBILE_TYPOGRAPHY } from "../../lib/typography"; import { Menu } from "../../components/ui/Menu"; @@ -421,7 +421,7 @@ export const ThreadComposer = function ThreadComposer(props: ThreadComposerProps const composerPanel = materialTheme["--color-composer-panel"]; const navigation = useNavigation(); const foregroundColor = useUniwindTheme()["--color-foreground"]; - const bodyText = useScaledTextRole("body"); + const bodyText = useComposerTextRole(); const fallbackInputRef = useRef(null); const inputRef = props.editorRef ?? fallbackInputRef; const [isFocused, setIsFocused] = useState(false); diff --git a/apps/mobile/src/lib/appearancePreferences.test.ts b/apps/mobile/src/lib/appearancePreferences.test.ts index 1bfedc7529c7c..5b1e9c33a90e1 100644 --- a/apps/mobile/src/lib/appearancePreferences.test.ts +++ b/apps/mobile/src/lib/appearancePreferences.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vite-plus/test"; -import { resolveAppearancePreferences } from "./appearancePreferences"; +import { resolveAppearancePreferences, resolveComposerTextRole } from "./appearancePreferences"; describe("appearancePreferences", () => { it("migrates the legacy markdownFontSize key to baseFontSize", () => { @@ -9,4 +9,14 @@ describe("appearancePreferences", () => { resolveAppearancePreferences({ baseFontSize: 16, markdownFontSize: 18 }).baseFontSize, ).toBe(16); }); + + it("scales the iOS composer text with the system text size", () => { + const body = { fontSize: 16, lineHeight: 23 }; + expect(resolveComposerTextRole(body, 1, "ios")).toEqual(body); + expect(resolveComposerTextRole(body, 2.643, "ios")).toEqual({ + fontSize: 16 * 2.643, + lineHeight: 23 * 2.643, + }); + expect(resolveComposerTextRole(body, 2.643, "android")).toEqual(body); + }); }); diff --git a/apps/mobile/src/lib/appearancePreferences.ts b/apps/mobile/src/lib/appearancePreferences.ts index 9b0be82b2c03f..6a7485e07c722 100644 --- a/apps/mobile/src/lib/appearancePreferences.ts +++ b/apps/mobile/src/lib/appearancePreferences.ts @@ -205,6 +205,17 @@ export function resolveScaledTextRole(role: keyof typeof MOBILE_TYPOGRAPHY, base }; } +// React Native text applies the system text size itself, but the iOS composer sets a fixed +// UIFont, so it needs pre-scaled metrics. Android's editor already sizes text in sp. +export function resolveComposerTextRole( + role: { readonly fontSize: number; readonly lineHeight: number }, + fontScale: number, + os: string, +) { + if (os !== "ios") return role; + return { fontSize: role.fontSize * fontScale, lineHeight: role.lineHeight * fontScale }; +} + export function resolveNativeMarkdownTypography(baseFontSize: number): NativeMarkdownTypography { const fontSizes = resolveMarkdownFontSizes(baseFontSize); return { diff --git a/apps/mobile/src/native/T3ComposerEditor.ios.tsx b/apps/mobile/src/native/T3ComposerEditor.ios.tsx index 437bcda4d012d..56f80be2e7be8 100644 --- a/apps/mobile/src/native/T3ComposerEditor.ios.tsx +++ b/apps/mobile/src/native/T3ComposerEditor.ios.tsx @@ -27,7 +27,7 @@ import { contextChipIconUri, modelChipIcon, modelChipIconUri } from "../lib/cont import { useUniwindTheme } from "../lib/useUniwindTheme"; import { createNativeComposerTheme } from "../lib/nativeComposerTheme"; import { FONT_FAMILIES } from "../lib/fontFamilies"; -import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole"; +import { useComposerTextRole } from "../features/settings/appearance/useScaledTextRole"; import { acknowledgeComposerNativeEvent, assumeComposerControlledState, @@ -176,7 +176,7 @@ export function ComposerEditor({ }); setConfirmedTokens({ value: props.value, tokens }); } - const bodyText = useScaledTextRole("body"); + const bodyText = useComposerTextRole(); const theme = useUniwindTheme(); const fontFamily = FONT_FAMILIES.regular;