feat(mobile): configurable font size with larger iOS default#2559
Open
dmarticus wants to merge 2 commits into
Open
feat(mobile): configurable font size with larger iOS default#2559dmarticus wants to merge 2 commits into
dmarticus wants to merge 2 commits into
Conversation
Add a user-configurable font size to the mobile app, and ship a larger
default on iOS where text reads noticeably smaller than Android at the same
point size.
The app has no central font-size token — sizes are set per-component via
NativeWind classes (text-sm, text-base, …) across many files. Rather than
touch every usage, this introduces a single global scale multiplier applied
at the one place font styling is already centralized: the `Text.render`
patch in `lib/textDefaults`.
- Add a `fontSize` preference ("small" | "default" | "large" | "xlarge")
to `preferencesStore`, persisted alongside the other preferences, with a
`FONT_SCALE_BY_PREFERENCE` map (0.9 / 1.0 / 1.15 / 1.3). The default is
"large" on iOS and "default" (1.0, unchanged) on Android.
- Extend the global `Text.render` patch to multiply each node's explicit
`fontSize` by the selected scale. Nodes without an explicit size are left
untouched so they keep inheriting their already-scaled parent — preserving
React Native's text-inheritance semantics for nested <Text>. Scaling is
skipped entirely when the multiplier is 1.
- Add a "Font size" row + select sheet under Settings → Appearance, matching
the existing discrete-picker pattern.
- Unit tests for the new preference and scale ordering.
Generated-By: PostHog Code
Task-Id: 6d87d732-7ac9-4633-89e2-d2be6efdcd67
|
React Doctor found no issues in the changed files. 🎉 Reviewed by React Doctor for commit |
Contributor
Prompt To Fix All With AIFix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
apps/mobile/src/features/preferences/stores/preferencesStore.test.ts:65-76
The `if/else` branch inside the `it.each` body is a superfluous part — the boolean flag `smaller` encodes the direction of comparison, but that direction could simply be expressed as an expected numeric value in each row. Passing exact values removes the conditional, makes each row self-documenting, and tests precise scale constants rather than just their ordering relative to 1.
```suggestion
it.each([
["small", 0.9],
["large", 1.15],
["xlarge", 1.3],
] as const)("FONT_SCALE_BY_PREFERENCE.%s equals %s", (size, expected) => {
expect(FONT_SCALE_BY_PREFERENCE[size]).toBe(expected);
});
```
Reviews (1): Last reviewed commit: "feat(mobile): configurable font size wit..." | Re-trigger Greptile |
Comment on lines
+65
to
+76
| it.each([ | ||
| ["small", true], | ||
| ["large", false], | ||
| ["xlarge", false], | ||
| ] as const)("orders the %s scale relative to default", (size, smaller) => { | ||
| const scale = FONT_SCALE_BY_PREFERENCE[size]; | ||
| if (smaller) { | ||
| expect(scale).toBeLessThan(1); | ||
| } else { | ||
| expect(scale).toBeGreaterThan(1); | ||
| } | ||
| }); |
Contributor
There was a problem hiding this comment.
The
if/else branch inside the it.each body is a superfluous part — the boolean flag smaller encodes the direction of comparison, but that direction could simply be expressed as an expected numeric value in each row. Passing exact values removes the conditional, makes each row self-documenting, and tests precise scale constants rather than just their ordering relative to 1.
Suggested change
| it.each([ | |
| ["small", true], | |
| ["large", false], | |
| ["xlarge", false], | |
| ] as const)("orders the %s scale relative to default", (size, smaller) => { | |
| const scale = FONT_SCALE_BY_PREFERENCE[size]; | |
| if (smaller) { | |
| expect(scale).toBeLessThan(1); | |
| } else { | |
| expect(scale).toBeGreaterThan(1); | |
| } | |
| }); | |
| it.each([ | |
| ["small", 0.9], | |
| ["large", 1.15], | |
| ["xlarge", 1.3], | |
| ] as const)("FONT_SCALE_BY_PREFERENCE.%s equals %s", (size, expected) => { | |
| expect(FONT_SCALE_BY_PREFERENCE[size]).toBe(expected); | |
| }); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/mobile/src/features/preferences/stores/preferencesStore.test.ts
Line: 65-76
Comment:
The `if/else` branch inside the `it.each` body is a superfluous part — the boolean flag `smaller` encodes the direction of comparison, but that direction could simply be expressed as an expected numeric value in each row. Passing exact values removes the conditional, makes each row self-documenting, and tests precise scale constants rather than just their ordering relative to 1.
```suggestion
it.each([
["small", 0.9],
["large", 1.15],
["xlarge", 1.3],
] as const)("FONT_SCALE_BY_PREFERENCE.%s equals %s", (size, expected) => {
expect(FONT_SCALE_BY_PREFERENCE[size]).toBe(expected);
});
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
- Collapse the react-native import in textDefaults to a single line so it matches Biome's formatter (the `quality` check was failing on this). - Assert exact FONT_SCALE_BY_PREFERENCE values instead of an if/else on ordering, per Greptile review feedback — each row is now self-documenting and pins the precise scale constants. Generated-By: PostHog Code Task-Id: 6d87d732-7ac9-4633-89e2-d2be6efdcd67
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two related asks for the mobile app:
Approach
The mobile app has no central font-size token — sizes are set per-component via NativeWind classes (
text-sm,text-base, …) across ~15+ files. Rather than touch every usage, this adds a single global scale multiplier at the one place text styling is already centralized: theText.renderpatch inlib/textDefaults.ts(which already injects the default font family).preferencesStore— new persistedfontSizepreference (small | default | large | xlarge) with aFONT_SCALE_BY_PREFERENCEmap (0.9 / 1.0 / 1.15 / 1.3). Default islargeon iOS,default(1.0, unchanged) on Android — so Android behavior is untouched and iOS ships larger out of the box.textDefaults.ts— the globalText.renderpatch now multiplies each node's explicitfontSizeby the selected scale. Nodes without an explicit size are left alone so they keep inheriting their already-scaled parent — this preserves React Native's text-inheritance semantics for nested<Text>. Scaling is skipped entirely when the multiplier is1.Decisions made (easy to change)
FONT_SCALE_BY_PREFERENCE.Notes
tscof the changed files is clean.🤖 Generated with Claude Code