Skip to content

Commit cb0553e

Browse files
authored
feat(markdown): render LaTeX across web and native mobile (#18735)
1 parent 0e40614 commit cb0553e

41 files changed

Lines changed: 1866 additions & 120 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/mobile/deps/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
The Enriched Markdown tarball comes from the [`fork/t3-mobile` branch](https://github.com/pingdotgg/enriched-markdown/tree/fork/t3-mobile). Its `t3-fork.json` records the exact source commit and upstream base. File/skill link recognition, back navigation from horizontal blocks, table-image presses, and code-block headers use upstream implementations. The remaining fork adds clipboard metadata, parsed asset manifests, signed image source resolution, React media slots, and development-host autolinks.
1+
The Enriched Markdown tarball comes from [`feat/t3chat-markdown-parity-v2`](https://github.com/pingdotgg/enriched-markdown/tree/feat/t3chat-markdown-parity-v2), based on Julius's [`fork/t3-mobile` branch](https://github.com/pingdotgg/enriched-markdown/tree/fork/t3-mobile). Its `t3-fork.json` records the exact source commit and upstream base. File/skill link recognition, back navigation from horizontal blocks, table-image presses, and code-block headers use upstream implementations. The fork retains clipboard metadata, parsed asset manifests, signed image source resolution, React media slots, and development-host autolinks. The fork retains native math parsing, scrolling and copying, table layout and gestures, and inline-code layout. Character-reference decoding, iOS intrinsic-width measurement, and HTML clipboard round trips now use the upstream fixes included in Julius's base. RaTeX uses the upstream-released 0.1.15 dependency.
22

3-
To replace it, commit the fork changes, run `yarn install --immutable` and `yarn workspace react-native-enriched-markdown prepare` in the fork checkout, then `node scripts/pack-t3.mjs /path/to/t3code/apps/mobile/deps`. Bump the fork package version for each replacement, update the tarball path in `apps/mobile/package.json` and `pnpm-workspace.yaml`'s `allowBuilds`, and run `vp i` to update the lockfile. Native clients need rebuilding when this dependency changes.
3+
To replace it, commit the fork changes, run `yarn install --immutable` and `yarn workspace react-native-enriched-markdown prepare` in the fork checkout, then `node scripts/pack-t3.mjs /path/to/t3code/apps/mobile/deps`. Keep the package version aligned with Julius's `fork/t3-mobile` branch. Rename the archive to include the first eight characters of the source commit, such as `react-native-enriched-markdown-1.2.0-t3.15-fee84114.tgz`, so each build has a distinct dependency path without version bumps. Update the tarball path in `apps/mobile/package.json` and `pnpm-workspace.yaml`'s `allowBuilds`, and run `vp i` to update the lockfile. Native clients need rebuilding when this dependency changes.
Binary file not shown.
-1.12 MB
Binary file not shown.

‎apps/mobile/package.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@
125125
"react-dom": "catalog:",
126126
"react-native": "0.88.0",
127127
"react-native-blob-util": "0.25.1",
128-
"react-native-enriched-markdown": "file:deps/react-native-enriched-markdown-1.2.0-t3.15.tgz",
128+
"react-native-enriched-markdown": "file:deps/react-native-enriched-markdown-1.2.0-t3.15-fee84114.tgz",
129129
"react-native-gesture-handler": "~3.3.0",
130130
"react-native-image-viewing": "^0.2.2",
131131
"react-native-keyboard-controller": "1.22.7",
@@ -159,7 +159,7 @@
159159
"typescript": "catalog:"
160160
},
161161
"enriched-markdown": {
162-
"enableMath": false,
162+
"enableMath": true,
163163
"enableVideo": false
164164
},
165165
"expo": {

‎apps/mobile/src/features/review/ReviewCommentCard.tsx‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { memo, useMemo, useState } from "react";
22
import { getFiletypeFromFileName } from "@pierre/diffs/utils/getFiletypeFromFileName";
33
import { ScrollView, StyleSheet, Text as NativeText, View, type ColorValue } from "react-native";
4+
import { SelectableMarkdownText } from "../../native/SelectableMarkdownText";
5+
import { useMarkdownPreviewStyles } from "../files/FileMarkdownPreview";
46
import { AppText as Text } from "../../components/ui/AppText";
57
import { SymbolView } from "../../components/ui/AppSymbol";
68
import { useUniwindTheme } from "../../lib/useUniwindTheme";
@@ -48,6 +50,7 @@ export const ReviewCommentCard = memo(function ReviewCommentCard(props: {
4850
readonly colors: ReviewCommentColors;
4951
}) {
5052
const { codeSurface, nativeReviewDiffStyle } = useAppearanceCodeSurface();
53+
const markdownStyles = useMarkdownPreviewStyles();
5154
const { themeAppearance: appearanceScheme, themeId } = useAppearancePreferences();
5255
const appTheme = useUniwindTheme();
5356
const [NativeReviewDiffView] = useState(() => resolveNativeReviewDiffView());
@@ -166,9 +169,10 @@ export const ReviewCommentCard = memo(function ReviewCommentCard(props: {
166169
</View>
167170
{props.comment.text.length > 0 ? (
168171
<View className="px-3 py-3">
169-
<Text selectable className="text-base leading-snug" style={{ color: props.colors.text }}>
170-
{props.comment.text}
171-
</Text>
172+
<SelectableMarkdownText
173+
markdown={props.comment.text}
174+
textStyle={markdownStyles.nativeTextStyle}
175+
/>
172176
</View>
173177
) : null}
174178
{shouldRenderNativeDiff ? (

‎apps/mobile/src/features/settings/appearance/AppearancePreferencesProvider.tsx‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ interface AppearancePreferencesContextValue {
6868
readonly setTerminalFontSize: (value: number | null) => void;
6969
readonly setCodeFontSize: (value: number | null) => void;
7070
readonly setCodeWordBreak: (value: boolean) => void;
71+
readonly mathRenderingEnabled: boolean;
72+
readonly setMathRenderingEnabled: (value: boolean) => void;
7173
}
7274

7375
const AppearancePreferencesContext = createContext<AppearancePreferencesContextValue | null>(null);
@@ -264,6 +266,12 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN
264266
[updatePreferences],
265267
);
266268

269+
const mathRenderingEnabled = storedPreferences?.mathRenderingEnabled === true;
270+
const setMathRenderingEnabled = useCallback(
271+
(value: boolean) => updatePreferences({ mathRenderingEnabled: value }),
272+
[updatePreferences],
273+
);
274+
267275
const value = useMemo(
268276
(): AppearancePreferencesContextValue => ({
269277
appearance,
@@ -284,6 +292,8 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN
284292
setTerminalFontSize,
285293
setCodeFontSize,
286294
setCodeWordBreak,
295+
mathRenderingEnabled,
296+
setMathRenderingEnabled,
287297
}),
288298
[
289299
appearance,
@@ -303,6 +313,8 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN
303313
setTerminalFontSize,
304314
setCodeFontSize,
305315
setCodeWordBreak,
316+
mathRenderingEnabled,
317+
setMathRenderingEnabled,
306318
],
307319
);
308320

‎apps/mobile/src/features/settings/appearance/sections/TextAppearanceSection.tsx‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import {
1010
TextAppearancePreview,
1111
} from "../components/AppearancePreviews";
1212
import { FontSizeSliderRow } from "../components/FontSizeSliderRow";
13+
import { SettingsSwitchRow } from "../../components/SettingsSwitchRow";
1314

1415
export function TextAppearanceSection() {
15-
const { isReady, appearance, setBaseFontSize } = useAppearancePreferences();
16+
const { isReady, appearance, setBaseFontSize, mathRenderingEnabled, setMathRenderingEnabled } =
17+
useAppearancePreferences();
1618

1719
return (
1820
<SettingsSection title="Text">
@@ -29,6 +31,13 @@ export function TextAppearanceSection() {
2931
value={appearance.baseFontSize}
3032
valueLabel={`${appearance.baseFontSize} pt`}
3133
/>
34+
<SettingsSwitchRow
35+
disabled={!isReady}
36+
icon="plusminus"
37+
label="Render math"
38+
value={mathRenderingEnabled}
39+
onValueChange={setMathRenderingEnabled}
40+
/>
3241
</SettingsSection>
3342
);
3443
}

‎apps/mobile/src/native/EnrichedMarkdownText.tsx‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ function enrichedStyle(
5959
};
6060
return {
6161
paragraph: body,
62+
math: {
63+
fontSize: style.fontSize,
64+
color: style.color,
65+
backgroundColor: "transparent",
66+
padding: 8,
67+
marginTop: 4,
68+
marginBottom: 12,
69+
},
70+
inlineMath: { color: style.color },
6271
h1: heading(1),
6372
h2: heading(2),
6473
h3: heading(3),
@@ -140,11 +149,11 @@ function enrichedStyle(
140149

141150
export function MobileEnrichedMarkdownText(props: SelectableMarkdownTextProps) {
142151
const { renderImage, resolveImageSource } = props;
152+
const { themeAppearance, mathRenderingEnabled } = useAppearancePreferences();
143153
const markdown = useMemo(
144154
() => markdownWithTableBreaks(markdownWithHtml(props.markdown)),
145155
[props.markdown],
146156
);
147-
const { themeAppearance } = useAppearancePreferences();
148157
const [documentAssets, setDocumentAssets] = useState<DocumentAssetsEvent["assets"]>([]);
149158
const onDocumentAssets = useCallback(({ assets }: DocumentAssetsEvent) => {
150159
setDocumentAssets((previous) =>
@@ -308,7 +317,7 @@ export function MobileEnrichedMarkdownText(props: SelectableMarkdownTextProps) {
308317
selectionColor={props.textStyle.selectionColor}
309318
selectionHandleColor={props.textStyle.selectionHandleColor}
310319
md4cFlags={{
311-
latexMath: false,
320+
latexMath: mathRenderingEnabled,
312321
hardSoftBreaks: props.preserveSoftBreaks ?? false,
313322
admonitions: true,
314323
}}

‎apps/mobile/src/persistence/mobile-preferences.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export interface Preferences {
6363
readonly markdownFontSize?: number;
6464
readonly codeFontSize?: number | null;
6565
readonly codeWordBreak?: boolean;
66+
readonly mathRenderingEnabled?: boolean;
6667
readonly connectOnboardingOptOutAccounts?: ReadonlyArray<string>;
6768
readonly collapsedProjectGroups?: readonly string[];
6869
/** Environments whose "reclaim database space" notice was dismissed. */
@@ -137,6 +138,7 @@ function sanitizePreferences(parsed: Preferences): Preferences {
137138
markdownFontSize?: number;
138139
codeFontSize?: number | null;
139140
codeWordBreak?: boolean;
141+
mathRenderingEnabled?: boolean;
140142
connectOnboardingOptOutAccounts?: ReadonlyArray<string>;
141143
collapsedProjectGroups?: readonly string[];
142144
dismissedDatabaseStorageNotices?: readonly string[];
@@ -206,6 +208,8 @@ function sanitizePreferences(parsed: Preferences): Preferences {
206208
preferences.codeFontSize = parsed.codeFontSize;
207209
}
208210
if (typeof parsed.codeWordBreak === "boolean") preferences.codeWordBreak = parsed.codeWordBreak;
211+
if (typeof parsed.mathRenderingEnabled === "boolean")
212+
preferences.mathRenderingEnabled = parsed.mathRenderingEnabled;
209213
if (Array.isArray(parsed.connectOnboardingOptOutAccounts)) {
210214
preferences.connectOnboardingOptOutAccounts = parsed.connectOnboardingOptOutAccounts.filter(
211215
(account): account is string => typeof account === "string",

‎apps/web/package.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"jose": "catalog:",
6262
"jsonc-parser": "3.3.1",
6363
"jszip": "3.10.1",
64+
"katex": "^0.16.47",
6465
"lucide": "^0.564.0",
6566
"lucide-react": "^0.564.0",
6667
"mermaid": "^11.17.2",
@@ -69,6 +70,7 @@
6970
"react-dom": "catalog:",
7071
"react-markdown": "^10.1.0",
7172
"remark-gfm": "catalog:",
73+
"remark-math": "6.0.0",
7274
"tailwind-merge": "^3.4.0",
7375
"unbash": "^5.0.0",
7476
"use-sync-external-store": "^1.6.0",

0 commit comments

Comments
 (0)