Header: font load#73389
Open
breville wants to merge 2 commits into
Open
Conversation
The code-studio header shifts horizontally early in page load as the Figtree text webfont and the Font Awesome icon webfont replace their fallbacks. Three independent causes, fixed here: - The help icon (.help_icon) reserves no width, so its Font Awesome glyph is zero-width until the font loads and then pops to 22px, shoving the adjacent user menu. Reserve a 22px glyph box; 22px matches the glyph advance, so the layout holds across the swap. - HeaderMiddle and its children measure text widths on mount, which on a cold load precedes the webfont, and never re-measure. The fallback font's widths then persist in the centering math (.header_middle is the flex remainder, so its width also moves as the side columns reflow). Re-measure on document.fonts.ready. - Eyes UI tests waited on document.fonts.status, which reads "loaded" whenever nothing is currently loading -- true before the page's fonts are even requested. Await the readiness promise instead, and a data-header-fonts-relaid-out flag the header sets after re-measuring, so captures land on the final layout. Pages without this header pass straight through and are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The signed-in user-menu chevron and create-menu plus are Font Awesome glyphs with no reserved width, so each collapses before the webfont loads and then expands, shifting the help icon and hamburger beside it ~4.7px. Reserve each glyph's box at its own advance (chevron 14px, plus 12.25px), the same fix the help icon got; the loaded layout is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
[written by Claude]
Stabilize the code-studio header across the webfont swap.
Summary
Early in page load the code-studio header shifts horizontally by a few pixels: icons (help
?, the user/create menu carets) pop as their glyphs load, and the centered lesson title and progress bubbles drift as the text font swaps in. The cause is webfonts arriving after first paint. This PR does two distinct things, without changing the header's final, fully-loaded appearance:Background
The header lays out its middle section in JavaScript: each piece (lesson title, progress, project info, finish link) measures its own rendered width and reports it up, and
HeaderMiddleuses those widths to allocate space and center the content. That measuring logic has been in place for years.In the time since, two things changed around it:
font-display: swap.Each was reasonable on its own. The interaction is what bites: the measuring code runs on mount — which on a cold load happens before the webfont arrives — and has no trigger to re-measure when a font finishes loading (it re-measures on window resize/scroll, but a font swap fires neither). So it measures against the fallback font and bakes those widths into the centering, where they persist unless some unrelated event re-measures.
The icons are a separate, older thread. The header has rendered Font Awesome icons since 2017, so the root behavior — an icon-font glyph has near-zero width until the font loads, then snaps to full size and shoves its neighbors when nothing reserves its box — is longstanding, not something introduced recently. (The icon class references were modernized from FA v4 to v6/v7 syntax in March 2026, but that doesn't change the collapse: an unreserved glyph has no width until its font arrives, regardless of version.) A latent issue all along, just easy to miss.
Together these produce a small, racy horizontal shift during load and intermittent eyes diffs — none of it visible once everything is cached/loaded.
What changed
1. Reserve the icon glyph boxes (CSS). The Font Awesome icons in the header had no reserved width, so they collapsed before the icon font loaded and shoved their neighbors when it arrived. Each now reserves a fixed box equal to its glyph advance, so the layout holds across the font swap:
?— 22pxBecause each width equals the glyph's own advance, the loaded appearance is unchanged. (The hamburger already reserved its box; the "MORE" caret sits in a fixed-width slot; the progress-bubble tooltip icons are off-screen — all left alone.)
2. Re-measure the header once fonts load (JS).
HeaderMiddleand its measuring children now re-measure ondocument.fonts.ready— which resolves only after the in-use webfonts (both Figtree and Font Awesome) have loaded and laid out — and correct the centering that was computed against the fallback font..header_middleis the flex remainder after the content-sized side columns, so its width also moves as those columns reflow; it is re-measured too.3. Make eyes captures deterministic (UI tests). The eyes wait used
document.fonts.status, which reads"loaded"whenever nothing is currently loading — true even before the page's fonts are requested. It now awaits thedocument.fonts.readypromise instead, plus adata-header-fonts-relaid-outflag the header sets after its re-measure, so a capture lands on the final layout. Pages that don't render this header (no.header_middle) pass straight through and are unaffected.Verification
Measured locally in a real browser (
document.fontsdriven to the fallback state and back):{w:14, top:59.5, h:14}and plus{w:12.25, top:17.5, h:14}are pixel-identical with and without the reservation.fonts.readyre-measure snaps it to the correct column.document.fonts.readyconfirmed to resolve with Font Awesome 6 Pro (900) loaded, not just the text font.Effect on eyes baselines
The PR is intended to be final-form-neutral. The icon reservations match each glyph's advance, so the loaded layout is unchanged and they should not move baselines. The only change that can move a baseline is the re-measure, and only where an existing baseline was captured in the stale (fallback-measured) state — in which case the new capture reflects the correct layout. Expect any baseline deltas to be small (~1–2px) and to represent the header settling where it always should have.
Notes
[test all browsers]).font-display: optionalon the header font, or a metric-matched fallback. Left out deliberately; happy to file a follow-up if we want the cold-load experience fully reflow-free.