fix(frontend): apply Geist to body text - #170
Merged
Merged
Conversation
Geist and Geist Mono are loaded in layout.tsx and their variables are
mapped to Tailwind's font-sans and font-mono, but globals.css still
carried the create-next-app default of `body { font-family: Arial,
Helvetica, sans-serif; }`. That override won everywhere, so Geist Sans
reached only elements that named `font-sans` explicitly, of which there
is exactly one in the whole application.
The result was that Geist Sans was downloaded on every page load and then
applied to almost nothing, while the interface rendered in Arial. Geist
Mono was unaffected, since its twenty call sites all use `font-mono`.
Setting the body font to the variable makes the loaded font the inherited
default, which is what the existing wiring already assumed. The variable
resolves to `Geist, Geist Fallback`, and that fallback is Arial with
size-adjust and metric overrides generated by Next, so the swap when the
webfont lands does not reflow the page. The `var()` default and the
trailing generics only apply if the font class is ever absent from body.
docs/DESIGN.md is updated in the same commit, since it documented the
previous behaviour.
Valtora
marked this pull request as ready for review
July 29, 2026 23:40
Valtora
added a commit
that referenced
this pull request
Jul 30, 2026
Bump docs/VERSION to 2.2.0 so the tag validates, and fill in the release-notes template for this range instead of hand-editing the published body afterwards, which is how v2.1.0 was done. A minor bump rather than a patch: #168 adds a capture action that did not exist before, stopping and processing a paused recording. Refs: #162, #163, #165, #167, #168, #169, #170, #171
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.
Pull Request
Description
Geist and Geist Mono are loaded in
layout.tsxvianext/font/google, their variables are applied to<body>, and@theme inlinemaps them to Tailwind's--font-sansand--font-mono. The wiring is complete. Butglobals.cssstill carried thecreate-next-appdefault:That override won everywhere, so Geist Sans reached only elements naming
font-sansexplicitly — exactly one in the entire application. Geist Sans was therefore downloaded on every page load and applied to almost nothing, while the interface rendered in Arial.Geist Mono was never affected: its twenty call sites all use the
font-monoutility, so it has been working as intended throughout.The fix sets the body font to the variable, making the loaded font the inherited default, which is what the existing wiring already assumed.
On the fallback chain
--font-geist-sansdoes not resolve to a bareGeist. It resolves toGeist, Geist Fallback, and Next generates that fallback as:That is Arial metrically adjusted to Geist, so it — not the trailing generics — is what covers the window before the webfont resolves, and it is why the swap should not visibly reflow the page.
Worth noting for review, because it is easy to get backwards:
var()with no default invalidates the whole declaration when the variable is missing, rather than falling through to the next family in the list. A trailingArialwould not have protected against an absent font class. The default is therefore written insidevar(), where it actually takes effect.No new dependencies.
Type of change
Non-breaking in the code sense, but it changes the typeface of every page, so it is a visible change rather than an invisible one.
Checks run
cd frontend && npm run lint— cleancd frontend && npm run test— 335 passed in 52 filescd frontend && npm run build— succeededpython3 scripts/validate_docs.py— 20 files, no broken linksVerified in the compiled output
The change was confirmed in the built CSS rather than assumed from the source:
Both halves are present, so the variable resolves and the rule applies.
Also checked: nothing draws text to a canvas (
ctx.font/fillText/measureTexthave no hits underfrontend/src), and after this change no otherArialorHelveticareference remains in the frontend, so nothing else depended on the old metrics. PDF and DOCX export render server-side and are untouched.Migration impact
Documentation impact
docs/DESIGN.mddocumented the previous behaviour, having been written from the code last week. Its typography section is corrected here, including the account of which part of the stack covers the font-load window.Security impact
Manual verification
Visual confirmation is outstanding, which is why this is a draft. No browser or headless tooling is available in this environment, so I verified the mechanism — the compiled rule, the variable's value, and the generated fallback's metric overrides — but I have not looked at a rendered page. Since this changes the typeface of every screen, someone should look before it merges.
Suggested pass, once running:
font-monowas already resolving to Geist Mono.data-ui-density="compact") are the places where a metric change would show first.Mark ready for review once that looks right; there is nothing else pending.