From 5508382337cf2f6d2612dfa30a0fd897cdd0baff Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 3 Nov 2026 14:14:52 +0000 Subject: [PATCH 1/4] fix(mobile): Android header and composer use DM Sans The Android screen header's Compose title and subtitle had no font family, and the native composer mapped every non-mono family to Typeface.DEFAULT, so both rendered in Roboto while chat text used DM Sans. Pass DMSans-Regular to the header's Compose Text, and resolve the composer's family through React Native's ReactFontManager, which holds the fonts expo-font registers. The placeholder follows the editor's typeface. Co-Authored-By: Claude Opus 5.5 --- apps/mobile/modules/t3-composer-editor/android/build.gradle | 1 + .../expo/modules/t3composereditor/T3ComposerEditorView.kt | 3 ++- apps/mobile/src/components/ui/AndroidScreenHeader.tsx | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/mobile/modules/t3-composer-editor/android/build.gradle b/apps/mobile/modules/t3-composer-editor/android/build.gradle index 9425c8f7de36b..fbb0d2d3767c0 100644 --- a/apps/mobile/modules/t3-composer-editor/android/build.gradle +++ b/apps/mobile/modules/t3-composer-editor/android/build.gradle @@ -20,6 +20,7 @@ android { dependencies { implementation project(':expo-modules-core') + implementation 'com.facebook.react:react-android' testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.16.1' } diff --git a/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorView.kt b/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorView.kt index 0fab5b17da8d6..68b2ff71ed9f3 100644 --- a/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorView.kt +++ b/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorView.kt @@ -30,6 +30,7 @@ import android.view.inputmethod.InputConnection import android.view.inputmethod.InputConnectionWrapper import android.view.inputmethod.InputMethodManager import android.widget.EditText +import com.facebook.react.common.assets.ReactFontManager import expo.modules.kotlin.AppContext import expo.modules.kotlin.viewevent.EventDispatcher import expo.modules.kotlin.views.ExpoView @@ -276,7 +277,7 @@ class T3ComposerEditorView(context: Context, appContext: AppContext) : ExpoView( editor.typeface = if (fontFamily.contains("Mono", ignoreCase = true)) { Typeface.MONOSPACE } else { - Typeface.DEFAULT + ReactFontManager.getInstance().getTypeface(fontFamily, Typeface.NORMAL, context.assets) } editor.applyPlaceholder() } diff --git a/apps/mobile/src/components/ui/AndroidScreenHeader.tsx b/apps/mobile/src/components/ui/AndroidScreenHeader.tsx index b7ab699b11b57..40cd2c73e1f18 100644 --- a/apps/mobile/src/components/ui/AndroidScreenHeader.tsx +++ b/apps/mobile/src/components/ui/AndroidScreenHeader.tsx @@ -10,6 +10,7 @@ import { IconButton } from "./IconButton"; import { AndroidAnchoredMenu } from "./AndroidAnchoredMenu"; import { splitAndroidHeaderActions } from "./headerMenu.android"; import { useScaledTextRole } from "../../features/settings/appearance/useScaledTextRole"; +import { FONT_FAMILIES } from "../../lib/fontFamilies"; import { useToolbarLayout } from "./useToolbarLayout"; import { useControlTheme } from "./useControlTheme"; import { RoutedFormSheetContext } from "./RoutedFormSheetContext"; @@ -50,8 +51,8 @@ export function AndroidScreenHeader(props: { readonly compact?: boolean; }) { const { colors } = useControlTheme(); - const titleTypography = useScaledTextRole("title"); - const subtitleTypography = useScaledTextRole("label"); + const titleTypography = { ...useScaledTextRole("title"), fontFamily: FONT_FAMILIES.regular }; + const subtitleTypography = { ...useScaledTextRole("label"), fontFamily: FONT_FAMILIES.regular }; const inFormSheet = use(RoutedFormSheetContext); const { paddingTop, paddingBottom } = useToolbarLayout(props.embedded || inFormSheet); const [headerWidth, setHeaderWidth] = useState(0); From daafb6c417f03a8328bc2f9bfb0497d7efae80c1 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 3 Nov 2026 14:20:33 +0000 Subject: [PATCH 2/4] fix(mobile): recompute composer line spacing after the font changes Co-Authored-By: Claude Opus 5.5 --- .../java/expo/modules/t3composereditor/T3ComposerEditorView.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorView.kt b/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorView.kt index 68b2ff71ed9f3..4d2a5e3a744f6 100644 --- a/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorView.kt +++ b/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorView.kt @@ -279,6 +279,7 @@ class T3ComposerEditorView(context: Context, appContext: AppContext) : ExpoView( } else { ReactFontManager.getInstance().getTypeface(fontFamily, Typeface.NORMAL, context.assets) } + applyLineHeight() editor.applyPlaceholder() } From 6227a9da7bc3f98ed7c2e33ce41b318904e97bf2 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 6 Nov 2026 15:32:19 -0800 Subject: [PATCH 3/4] fix(mobile): Android composer chips use DM Sans Medium Co-Authored-By: Claude Opus 5.5 --- .../modules/t3composereditor/T3ContextChip.kt | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ContextChip.kt b/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ContextChip.kt index b181c663df54b..05aa101655fbe 100644 --- a/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ContextChip.kt +++ b/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ContextChip.kt @@ -9,6 +9,7 @@ import android.graphics.RectF import android.graphics.Typeface import android.text.TextPaint import android.text.TextUtils +import com.facebook.react.common.assets.ReactFontManager import kotlin.math.ceil import kotlin.math.min @@ -29,14 +30,14 @@ class T3ContextChip( private val paint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply { textSize = fontSize - typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL) + typeface = chipTypeface() color = this@T3ContextChip.color } private val em = fontSize private val detailPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply { textSize = fontSize * 0.84f - typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL) + typeface = chipTypeface() color = this@T3ContextChip.color } private val detailText = if (content.detail.isEmpty()) "" else " ${content.detail}" @@ -86,6 +87,18 @@ class T3ContextChip( } companion object { + private const val CHIP_FONT_FAMILY = "DMSans-Medium" + + // Matches the iOS chips; builds without the bundled font keep the platform medium. + private fun chipTypeface(): Typeface { + val fonts = ReactFontManager.getInstance() + return if (CHIP_FONT_FAMILY in fonts.customFontFamilies) { + fonts.getTypeface(CHIP_FONT_FAMILY, Typeface.NORMAL, null) + } else { + Typeface.create("sans-serif-medium", Typeface.NORMAL) + } + } + fun color( value: String, fallback: Int From 7d68b93d58b9fa4ad6b188924ae8299170f9a07b Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 6 Nov 2026 18:49:34 -0800 Subject: [PATCH 4/4] fix(mobile): keep the Android header on the system font Native chrome uses the system font on both platforms (the iOS header is SF), so this PR only fixes the Android composer. Co-Authored-By: Claude Opus 5.5 --- apps/mobile/src/components/ui/AndroidScreenHeader.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/mobile/src/components/ui/AndroidScreenHeader.tsx b/apps/mobile/src/components/ui/AndroidScreenHeader.tsx index 40cd2c73e1f18..b7ab699b11b57 100644 --- a/apps/mobile/src/components/ui/AndroidScreenHeader.tsx +++ b/apps/mobile/src/components/ui/AndroidScreenHeader.tsx @@ -10,7 +10,6 @@ import { IconButton } from "./IconButton"; import { AndroidAnchoredMenu } from "./AndroidAnchoredMenu"; import { splitAndroidHeaderActions } from "./headerMenu.android"; import { useScaledTextRole } from "../../features/settings/appearance/useScaledTextRole"; -import { FONT_FAMILIES } from "../../lib/fontFamilies"; import { useToolbarLayout } from "./useToolbarLayout"; import { useControlTheme } from "./useControlTheme"; import { RoutedFormSheetContext } from "./RoutedFormSheetContext"; @@ -51,8 +50,8 @@ export function AndroidScreenHeader(props: { readonly compact?: boolean; }) { const { colors } = useControlTheme(); - const titleTypography = { ...useScaledTextRole("title"), fontFamily: FONT_FAMILIES.regular }; - const subtitleTypography = { ...useScaledTextRole("label"), fontFamily: FONT_FAMILIES.regular }; + const titleTypography = useScaledTextRole("title"); + const subtitleTypography = useScaledTextRole("label"); const inFormSheet = use(RoutedFormSheetContext); const { paddingTop, paddingBottom } = useToolbarLayout(props.embedded || inFormSheet); const [headerWidth, setHeaderWidth] = useState(0);