Skip to content

[Typography] Add semantic type tokens and block new raw font sizes - #96880

Merged
blimpich merged 32 commits into
mainfrom
georgia-font-lib
Aug 25, 2026
Merged

[Typography] Add semantic type tokens and block new raw font sizes#96880
blimpich merged 32 commits into
mainfrom
georgia-font-lib

Conversation

@grgia

@grgia grgia commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

Text styles now come from a typography token layer (src/styles/typography.ts), and new raw fontSize/lineHeight literals are blocked by lint.

Type styles have drifted with nothing to stop it: fontSizeH1 and fontSizeH2 are both 19, and ~75 text* styles express about a dozen real roles because color is baked into type styles. The token file defines a primitive scale plus semantic variants named after the Figma type styles, and the existing text* styles are aliased to those tokens with color re-attached at the alias boundary. Design confirmed the Figma library is the source of truth for values, so this PR also aligns the drifted values (headline line height 32→28, label 18→16, micro strong 16→14, intro headline 45→44, extra small button text line height to 12) and moves the oversized Kansas 28 headings on the loading, magic link, and confirmation screens onto H1 (22/28), per the design decisions on the issue. Everything else renders exactly as before.

image
  • Variants cover all 14 Figma Product/* styles 1:1
  • Loading, magic link, and confirmation screen headings migrated to H1
  • <Text variant="..."> added, fontSize prop deprecated
  • Existing violations grandfathered via eslint-seatbelt
  • QRShare migrated as the worked example
  • Headline, label, micro strong, intro headline line heights aligned to Figma
  • introHeadline matches the new Figma Product/Intro Headline style
  • Other call-sites heal as edited
  • Unit tests cover the lint rule and variant prop

Fixed Issues

$ #37503
PROPOSAL:

Tests

  1. Run npm run test -- tests/unit/NoRawTypographyRuleTest.ts tests/unit/TextVariantTest.tsx and verify both suites pass.
  2. Add fontSize: 17 to any component style, run npm run lint on that file, and verify the rulesdir/no-raw-typography error appears.
  3. Open any chat, verify headings, message text, labels, and timestamps render identically to main.
  4. Go to Settings > Share code and verify the title and subtitle spacing.
  5. Open a workspace overview page and verify section titles and form labels look right (slightly tighter line heights than production, per design).
  6. Open a brand-new chat and verify the large welcome headline renders correctly.
  7. Sign out, enter your email, and verify the magic code modal heading renders at the standard heading size (same as other page headings).
  • Verify that no errors appear in the JS console

Offline tests

None, style definitions only. No network-dependent behavior.

QA Steps

  1. Open any chat and a Settings page.
  2. Verify headings, section titles, and labels render slightly tighter than production (headline 28px, label 16px line heights) with no clipping or overlap.
  3. Sign out and request a magic code, and verify the modal heading is smaller than production (22px instead of 28px) with no clipping.
  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari

@github-actions

Copy link
Copy Markdown
Contributor

🚧 grgia has triggered a test Expensify/App build. You can view the workflow run here.

@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

🚧 grgia has triggered a test Expensify/App build. You can view the workflow run here.

@github-actions

This comment has been minimized.

Comment on lines 108 to +109
<Text
fontSize={variables.fontSizeLabel}
variant="label"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example

@github-actions

Copy link
Copy Markdown
Contributor

🚧 grgia has triggered a test Expensify/App build. You can view the workflow run here.

@github-actions

This comment has been minimized.

@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
...equestReportView/MoneyRequestReportGroupHeader.tsx 100.00% <100.00%> (ø)
src/components/QRShare/index.tsx 0.00% <ø> (ø)
src/components/SAMLLoadingIndicator.tsx 100.00% <100.00%> (ø)
src/components/Text.tsx 100.00% <100.00%> (ø)
...mponents/ValidateCode/ExpiredValidateCodeModal.tsx 100.00% <100.00%> (ø)
src/components/ValidateCode/JustSignedInModal.tsx 95.00% <100.00%> (ø)
src/components/ValidateCode/ValidateCodeModal.tsx 100.00% <100.00%> (ø)
src/pages/ErrorPage/SessionExpiredPage.tsx 100.00% <100.00%> (ø)
src/styles/index.ts 63.00% <ø> (ø)
src/styles/typography.ts 100.00% <100.00%> (ø)
... and 2 more
... and 176 files with indirect coverage changes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@grgia grgia changed the title [HOLD - TESTING][Typography] Add semantic type tokens and block new raw font sizes [Typography] Add semantic type tokens and block new raw font sizes Aug 11, 2026
@grgia
grgia marked this pull request as ready for review August 11, 2026 16:03
@grgia
grgia requested review from a team as code owners August 11, 2026 16:03
@grgia
grgia requested a review from aimane-chnaif August 18, 2026 10:27
@grgia

grgia commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@aimane-chnaif yes ready for review

@grgia

grgia commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

I made the change in the issue thread

@github-actions

Copy link
Copy Markdown
Contributor

🚧 grgia has triggered a test Expensify/App build. You can view the workflow run here.

@github-actions

This comment has been minimized.

@grgia

grgia commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

@aimane-chnaif bump on review

@aimane-chnaif

Copy link
Copy Markdown
Contributor

Reviewing

@aimane-chnaif aimane-chnaif left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some finds based on code review: (still testing & verifying)

Comment thread src/components/Text.tsx
fontSize,
textAlign,
...FontUtils.fontFamily.platform[family],
...(variant ? textVariants[variant] : undefined),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

variant overrides explicitly-passed fontSize and family

Comment thread src/styles/index.ts
fontSize: variables.fontSizeLabel,
lineHeight: variables.lineHeightLarge,
fontSize: fontScale.label,
lineHeight: lineHeightScale.label,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

label line height drops 18 → 16

Comment thread src/styles/typography.ts
Comment on lines +63 to +67
microStrong: {
...fontFamilyScale.strong,
fontSize: fontScale.micro,
lineHeight: lineHeightScale.micro,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

microStrong line box is shorter than its glyphs at high pixel ratio

Comment thread src/styles/index.ts
Comment on lines 527 to +532
textMicroBold: {
...textVariants.microStrong,
color: theme.text,
...FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
fontSize: variables.fontSizeSmall,
lineHeight: variables.lineHeightNormal,
},

textMicroBoldSupporting: {
...textVariants.microStrong,

@aimane-chnaif aimane-chnaif Aug 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

textMicroBold line box becomes smaller than its own glyphs at large text sizes

main PR
fontSize 11 / 17 11 / 17
lineHeight 16 / 21 14 / 16

At max accessibility text size the font caps at 17px inside a 16px line box, so descenders clip. Affects 12 files.

Repro:

  1. iOS Settings → Accessibility → Display & Text Size → Larger Text → enable and drag the slider to maximum
  2. Open New Expensify → Search tab
  3. Tap any filter pill at the top (Type / Status / Date)
  4. Pill labels (src/components/Search/FilterDropdowns/DropdownButton.tsx:97) render clipped vs. main

src/components/Table/TableHeader.tsx:279 is also affected as lh16 sits first in the array:

style={[styles.lh16, isSortingByColumn ? styles.textMicroBoldSupporting : styles.textMicroSupporting]}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above case is not reproducible but there might be other places where text bottom is cut off.
Not found so far.

/>
</View>
<Text style={[styles.textHeadline, styles.textXXLarge, styles.textAlignCenter]}>{translate('validateCodeModal.expiredCodeTitle')}</Text>
<Text style={[styles.textHeadline, styles.textAlignCenter]}>{translate('validateCodeModal.expiredCodeTitle')}</Text>

@aimane-chnaif aimane-chnaif Aug 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Six full-screen headings shrink ~21% at normal text size

styles.textXXLarge was dropped from six headings, so they fall back to textHeadline's size:

  • fontSize 28 → 22 at normal device text size (both are 28 at max, so this only shows at normal settings)
  • lineHeight 32 → 28

Screens: SAMLLoadingIndicator.tsx:30, ValidateCode/ExpiredValidateCodeModal.tsx:40, ValidateCode/JustSignedInModal.tsx:50, ValidateCode/ValidateCodeModal.tsx:65, pages/ConnectionCompletePage.tsx:29, pages/ErrorPage/SessionExpiredPage.tsx:36

Repro:

  1. Sign out
  2. Enter your email → request a magic code
  3. Open the emailed link twice (the second open hits the expired path)
  4. The "Security code has expired" headline is visibly smaller than on main

Direct route without the email round-trip: /connection-complete

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's comparison

this branch:
Screenshot 2026-08-25 at 5 47 59 PM

production:
Screenshot 2026-08-25 at 5 47 40 PM

@aimane-chnaif

Copy link
Copy Markdown
Contributor

Intentional per commit history, but user-visible

These trace to explicit commits ("Align headline, label, and micro strong values with the Figma library", "Size suggestion menu emojis like inline emojis and labels like body text"). Flagging so the blast radius is a conscious call:

Style main PR Reach
label / textLabel / mutedTextLabel lineHeight 18 / 24 16 / 21 67 files
textHeadline lineHeight 32 / 37 28 / 32 48 files
emojiSuggestionsText, mentionSuggestionsText 16 / 22 15 / 21 @ and : popups
emojiSuggestionsEmoji 16 / 22 17 / 19 bigger normally, smaller at max text size
textHero lineHeight 45 44

Repro for the label change:

  1. Open any expense
  2. Field labels ("Description", "Category", "Merchant") sit tighter
  3. Most visible where a label wraps to two lines — e.g. Workspace → Categories with a long category name

@aimane-chnaif

Copy link
Copy Markdown
Contributor

I performed full regression test as many cases as possible but I might have missed places where bottom part of text is cut off due to reduced lineHeight.
We can watch out and fix them whenever reported.

@aimane-chnaif

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
android.mov
Android: mWeb Chrome
iOS: HybridApp
ios.mov
iOS: mWeb Safari
MacOS: Chrome / Safari
web.mov

@aimane-chnaif aimane-chnaif left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check NAB comments above

Comment thread src/styles/index.ts
Comment on lines 499 to +509
mutedTextLabel: {
color: theme.textSupporting,
fontSize: variables.fontSizeLabel,
lineHeight: variables.lineHeightLarge,
fontSize: fontScale.label,
lineHeight: lineHeightScale.label,
},

mutedNormalTextLabel: {
color: theme.textSupporting,
fontSize: variables.fontSizeLabel,
lineHeight: variables.lineHeightNormal,
fontSize: fontScale.label,
lineHeight: lineHeightScale.label,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now 2 style keys have exact same attributes (same color, same size, same line height)

Comment thread src/styles/index.ts
Comment on lines 629 to 618
textHeadlineH1: {
...headlineFont,
...whiteSpace.preWrap,
...textVariants.h1,
color: theme.heading,
fontSize: variables.fontSizeXLarge,
lineHeight: variables.lineHeightSizeH1,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now textHeadlineH1 is exactly the same as textHeadline

const shouldShowCheckbox = isSelectionModeEnabled || !shouldUseNarrowLayout;

const textStyle = shouldUseNarrowLayout ? {fontSize: variables.fontSizeLabel, lineHeight: 16} : [styles.labelStrong];
const textStyle = shouldUseNarrowLayout ? {fontSize: variables.fontSizeLabel, lineHeight: variables.lineHeightNormal} : [styles.labelStrong];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This inline style is now exactly the same as styles.label

@melvin-bot
melvin-bot Bot requested a review from blimpich August 25, 2026 17:13
@melvin-bot

melvin-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown

@blimpich Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 61dbba653e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

context.report({
node: valueNode,
messageId: 'rawTypography',
data: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Check direct typography assignments

When a style is built or mutated with an assignment such as style.fontSize = 17 or style.lineHeight = 20, the parser emits an AssignmentExpression, so neither listener runs and the new lint gate accepts the raw typography value. This is valid for a TextStyle in both TypeScript and JavaScript; add handling for statically named member assignments and cover both properties in the rule tests.

Useful? React with 👍 / 👎.

@MelvinBot

Copy link
Copy Markdown
Contributor

Review

Code LGTM. The token layer, the no-raw-typography lint rule, and the Text variant prop are clean, well-documented, and covered by unit tests. All CI is green (the only red check is Check independent approval, a review-process gate). The main residual risk is purely visual — the intentional value realignments — and I was not able to complete the local visual QA (steps 3–7). See the testing note at the bottom.

What I checked in the code
  • no-raw-typography rule (eslint-plugin-local-rules/no-raw-typography.js) — correctly unwraps TS assertion wrappers (as/satisfies/!), handles both object properties and JSX attributes, and treats unary +/- literals as raw. Solid.
  • Text precedence (src/components/Text.tsx:74) — variant spreads after fontSize/family and before style/customStyle, so style still wins over variant (asserted in TextVariantTest). The default-line-height branch at Text.tsx:79 can't conflict with a variant since every variant defines lineHeight. 👍
  • Alias values — spot-checked against variables.ts: textHeadline line height 32→28 (lineHeightXXXLargelineHeightSizeH1), label 18→16 (lineHeightLargelineHeightNormal), and the textXXLarge drop on the SAML / ValidateCode / ConnectionComplete / SessionExpired headings (28→22 via H1) all match the changes described. These are the design-approved realignments, so they're the only things a visual pass needs to confirm.
  • Breadcrumbs deletion — no references remain under src/, so removing it (rather than migrating its raw sizes) is safe, if slightly outside the PR's stated scope.
  • The git diff between this branch and main shows some unrelated churn in eslint.config.mjs/eslint.seatbelt.tsv, but that's just branch staleness against main; the PR's own diff (via GitHub) only adds the rule registration plus the typography.ts/variables.ts opt-out. No concern.

One minor thing worth a look: in MoneyRequestReportGroupHeader.tsx:83 the raw lineHeight: 16 becomes variables.lineHeightNormal, which is getValueUsingPixelRatio(16, 21). On the smaller pixel ratio that's identical (16), but on the larger ratio it now resolves to 21 instead of a fixed 16. That's arguably a fix (it now scales like the surrounding fontSizeLabel and matches the new label token), but it's a behavior change rather than a pure no-op — worth a glance on a high-density layout to confirm nothing shifts.


⚠️ Testing note — local visual QA (steps 3–7) could not be completed

The web test session never got past the boot splash — all JS bundles and fonts loaded (200, no 4xx/5xx), but React never mounted and no further requests fired for 6+ minutes, so no chat/settings/workspace/welcome/sign-out screen ever rendered. This looks like a test-environment bring-up failure, not a regression in this PR, because the deployed adhoc web build works and every build/perf/storybook/unit check is green (a module-init crash in the font code would have failed those).

Because of that, the intentional visual changes (steps 3–7) are the one thing still worth a manual eyeball before merge — most importantly step 7 (magic-code modal heading now 22px vs 28px) and the tighter headline/label line heights. Easiest path is the adhoc build already posted above: https://96880.pr-testing.expensify.com

Verification results (all blocked on boot)
Step Status Observation
App boots past splash ❌ fail snapshot returned 0 nodes across 6+ min; screenshots minutes apart are pixel-identical; all bundles/fonts 200
1 — chat text rendering ❌ blocked app never reached any screen
2 — Share code QR heading/subtitle ❌ blocked app never reached any screen
3 — workspace section titles/labels ❌ blocked app never reached any screen
4 — empty-chat welcome headline ❌ blocked app never reached any screen
5 — magic-code modal heading size ❌ blocked app never reached any screen

Screenshots of the boot-splash hang:

Evidence (2)

Local test app stuck on boot splash during prestart sign-in

Local test app stuck on boot splash during prestart sign-in

Identical boot splash ~6 min later — app never mounted

Identical boot splash ~6 min later — app never mounted

view run

@MelvinBot

Copy link
Copy Markdown
Contributor

Regression scan

No logic/functional regressions found. The risk in this PR is purely value drift — a text* style getting aliased to a token whose computed value differs from the original at a call-site the description doesn't call out. I diffed every aliased style's old-vs-new computed value against variables.ts. Everything lines up with the design-approved realignments, with two user-visible changes that aren't documented in the PR body and are worth a QA glance:

1. Autocomplete suggestion menus resized (not in the description) — worth confirming with design

src/styles/index.ts:358 emojiSuggestionsEmoji, :363 emojiSuggestionsText, and :382 mentionSuggestionsText move off fontSizeMedium (16/22):

  • emoji → fontSizeEmojisWithinText (17/19): +1px small ratio, −3px large ratio
  • suggestion + mention text → fontSizeNormal (15/21): −1px both ratios

This is a deliberate commit ("Size suggestion menu emojis like inline emojis and labels like body text"), but it's not listed in the "aligns the drifted values" section, so a reviewer/QA reading the description wouldn't know to check the @mention and :emoji: autocomplete popups. Please eyeball those and confirm design signed off, or add them to the description.

2. MoneyRequestReportGroupHeader amount line-height now scales with pixel ratio

MoneyRequestReportGroupHeader.tsx:83 swaps raw lineHeight: 16 for variables.lineHeightNormal (getValueUsingPixelRatio(16, 21)). Identical on the small ratio (16), but 16→21 on the large ratio for the narrow-layout subtotal text. Low severity (arguably a fix — now consistent with the label token), but it's a behavior change not in the value-change list.

✅ Verified safe — the one place that looks scary but isn't

The big balance/amount display (Balance.tsx, [textHeadline, textXXXLarge]) renders identically. textHeadline's line height drops 32→28 (h1), but textXXXLarge:588 gains lineHeight: lineHeightXXXLarge (32/37) which wins (later in the array), and the font family is unchanged (headlineFont and h1 are both EXP_NEW_KANSAS_MEDIUM). Net fontSize/lineHeight/family are unchanged — the added line-height is an exact compensation, and textXXXLarge has no other call-sites. 👍

Everything else checked out as a no-op or as a documented intentional change
  • No-op aliases (old computed value == new): textMicro, textMicroSupporting, textMicroBold, textSmall, textSupportingNormal, textNormal, textNormalThemeText, labelStrong (already lineHeightNormal), mutedNormalTextLabel (already lineHeightNormal), widgetItemTitle, chartTitle (→ textStrong), textHeadlineH1, textHeadlineH2, exportDownloadTitle. whiteSpace.preWrap is preserved on h1/h2/pageHeader.
  • Documented intentional realignments: headline line height 32→28 (all textHeadline), label 18→16 (label/textLabel/mutedTextLabel — broad, every form label; covered by QA step 5), micro strong 16→14, intro headline (textHero) 45→44, extra-small button text → finePrintStrong (line height 12), and the Kansas-28 → H1 (22/28) headings on the SAML / ValidateCode / ConnectionComplete / SessionExpired screens (removal of textXXLarge).
  • Text component: the variant spread sits before style/customStyle (style still wins), and the default-line-height fallback at Text.tsx:79 can't conflict with a variant since every variant defines lineHeight. No behavior change for existing fontSize/style callers.
  • Breadcrumbs deletion: genuinely unused (no src/ references) — safe.
  • All CI green (build / perf / storybook / unit / knip / unused-styles), which also rules out a module-init crash in the token layer.

Runtime verification is still blocked by the same environment boot failure from the review above — the local test session never mounts React past the boot splash (an infra/session issue, not a PR regression, given green CI + a working adhoc build). So findings 1 and 2 above are worth a manual eyeball on the adhoc build before merge: https://96880.pr-testing.expensify.com


view run

@blimpich
blimpich merged commit 7daf0ee into main Aug 25, 2026
47 of 59 checks passed
@blimpich
blimpich deleted the georgia-font-lib branch August 25, 2026 21:36
@github-actions

Copy link
Copy Markdown
Contributor

🚧 blimpich has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/blimpich in version: 9.4.62-1 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@MelvinBot

Copy link
Copy Markdown
Contributor

Help site review: no changes required

I reviewed the changes in this PR against the help site content in App/docs/articles, and no documentation updates are needed.

Why: This PR is an internal frontend/design-system refactor. It:

  • Introduces a typography token layer (src/styles/typography.ts) and a lint rule blocking new raw fontSize/lineHeight literals
  • Adds a variant prop to the Text component and deprecates the fontSize prop
  • Aligns a handful of drifted line-height/font-size values to Figma and moves a few oversized headings (loading, magic link, confirmation screens) onto the H1 style

None of this changes a customer-facing feature, workflow, setting, tab, or button. The help site articles document how to use Expensify (billing, workspaces, expenses, integrations, etc.) — they never document typography tokens, font sizes, or line heights. The only user-visible effects here are subtle rendering refinements (slightly tighter line heights, a smaller magic-code modal heading), which aren't described in any article.

Since no help site changes are required, I did not create a draft docs PR.

@grgia, if you believe a specific article is affected by a behavior change I missed, let me know which one and I'll take another look.


view run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants