perf(frontend): preload the two first-paint Fira Code faces - #5992
Merged
Conversation
The app font faces are declared in static/style.css, so the browser only discovers FiraCode-Regular.woff2 and FiraCode-SemiBold.woff2 once the stylesheet has parsed. On a cold load they start about 145 ms in, behind the module graph. font-display: swap keeps that from blocking render, so the cost is a visible swap rather than a stall, but the fetch can start immediately instead. Two preload hints move the request into the head. Measured cold on a scratch instance with an empty cache, three runs per arm: request start 142-203 ms becomes 15-19 ms, response end 174-248 ms becomes 46-63 ms. The total request count is unchanged and each face is still fetched exactly once. crossorigin is required even though these are same-origin: fonts are always fetched in CORS mode, and without it the preload is discarded and the font fetched again. Dropping the attribute produces four font entries in the Resource Timing list instead of two. Only Fira Code 400 and 600 are preloaded. They are the only faces first paint uses. Inter, OpenDyslexic and Fira Code 300 stay unloaded on both desktop and mobile, with or without a saved font preference.
15 tasks
RaresKeY
approved these changes
Aug 11, 2026
RaresKeY
left a comment
Member
There was a problem hiding this comment.
I reviewed the first-paint font preloads on the current head and did not find an author-actionable issue.
Validation
- The hints target the exact Fira Code 400 and 600 WOFF2 files used by the stylesheet, precede stylesheet discovery, and use the destination, MIME type, URL, and anonymous CORS mode needed for request reuse.
- The existing CSP, static-file, cache, and service-worker boundaries remain unchanged.
- The current substantive GitHub checks pass. I did not independently reproduce the browser waterfall, so cross-browser, theme-specific, and constrained-network timing remains the residual validation gap.
cybervand
pushed a commit
to cybervand/odysseus
that referenced
this pull request
Aug 12, 2026
…-dev#5992) The app font faces are declared in static/style.css, so the browser only discovers FiraCode-Regular.woff2 and FiraCode-SemiBold.woff2 once the stylesheet has parsed. On a cold load they start about 145 ms in, behind the module graph. font-display: swap keeps that from blocking render, so the cost is a visible swap rather than a stall, but the fetch can start immediately instead. Two preload hints move the request into the head. Measured cold on a scratch instance with an empty cache, three runs per arm: request start 142-203 ms becomes 15-19 ms, response end 174-248 ms becomes 46-63 ms. The total request count is unchanged and each face is still fetched exactly once. crossorigin is required even though these are same-origin: fonts are always fetched in CORS mode, and without it the preload is discarded and the font fetched again. Dropping the attribute produces four font entries in the Resource Timing list instead of two. Only Fira Code 400 and 600 are preloaded. They are the only faces first paint uses. Inter, OpenDyslexic and Fira Code 300 stay unloaded on both desktop and mobile, with or without a saved font preference.
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.
Summary
The two font faces first paint uses,
FiraCode-Regular.woff2andFiraCode-SemiBold.woff2, are declared instatic/style.css, so the browser cannot discover them until the stylesheet has been fetched and parsed. On a cold load the font request goes out around 145 ms in, behind the module graph.font-display: swapkeeps that from blocking render, so the cost is a longer window of fallback text rather than a stall, but there is no reason the fetch cannot start immediately. This adds tworel="preload"hints in<head>for exactly those two faces. Measured cold with an empty cache, three runs per arm: request start goes from 142-203 ms to 15-19 ms and response end from 174-248 ms to 46-63 ms, with the total request count unchanged and each face still fetched exactly once.Target branch
dev, notmain.Linked Issue
Closes #5991
Type of Change
There is no performance box. Refactor is the closest fit: nothing user-visible changes, only when the font request is issued.
Checklist
static/index.html; only feat(agent): add exact approvals and run modes #5819 and feat(agent): persist provenance and gate sensitive egress #5821 touch the<head>link block, and both only bump the?v=query string on the existingmodulepreloadlines. The added lines are adjacent to those, so whichever of us lands second needs a trivial rebase.devuvicorn app:app) and verified the change works end-to-end.How to Test
The measurement needs a genuinely cold cache, so use a fresh browser profile for each run rather than a reload. A service-worker unregister plus cache clear is not enough on its own; the HTTP cache will still serve the fonts as 304s and the numbers collapse to noise.
uvicorn app:app --host 127.0.0.1 --port 7099.devyou get two entries withinit=cssstarting around 142-203 ms. On this branch you get two entries withinit=linkstarting around 15-19 ms. Two entries either way, andperformance.getEntriesByType('resource').lengthis 226 in both cases.Confirm
crossoriginis doing its job, because without it this change is worse than no change at all:Two font entries in the Resource Timing list means the preload was consumed. If you strip the
crossoriginattribute and reload cold you get four instead: the two preloads at about 17 ms, then the same two files again at about 149 ms withinit=css. I checked that rather than trusting the folklore.Confirm nothing else got dragged in. Everything except Fira Code 400 and 600 should still be
unloaded:Inter, OpenDyslexic and Fira Code 300 stay
unloadedthrough a full cold boot, on a 1280x800 desktop viewport and a 390x844 mobile one, and withlocalStorage['odysseus-theme']set to{"font":"sans"}as well as unset. That last one matters: enough of the UI hardcodes'Fira Code', monospacerather thanvar(--font-family)that both faces load regardless of the theme's font setting, so the hint is never wasted.Console should be clean. No "preloaded but not used" warning, no unsupported
asvalue warning. The only messages I get on a cold load areTTS: not availableandHighlighting all code blocks on page load, both of which are there on unmodifieddevtoo.Full suite on this branch:
4902 passed, 2 failed, 4 skippedin 140.30 s. The two failures aretests/test_workspace_confine.py::test_glob_confined_e2e(/tmpresolving to/private/tmpon macOS) andtests/test_integration_api_call_ssrf.py::test_real_socket_falls_back_from_dead_first_to_live_second(real sockets, connect-refused timing). Both are the known environmental pair and reproduce identically on an unmodified checkout.python -m compileallonapp.py core routes src services scripts testsexits 0. No JS or Python file is touched by this diff, so there is nothing fornode --checkto run against.No test is added. A preload hint has no runtime behaviour to assert, and there is no HTML-linting harness in the repo, so the pytest-shells-out-to-Node idiom the JS tests use does not apply here. Happy to add a smoke test that greps
index.htmlfor the two hints if you want one, but it would only restate the diff.Visual / UI changes
@font-facerules,--font-familyand every existing<link>are unchanged. The two added lines are resource hints, not markup that renders.Screenshots / clips
Flagging this honestly rather than ticking the box. Steady-state rendering is identical: a before and after screenshot of the loaded app are the same picture, because the same two faces are used either way and only the moment they arrive changes. The one real visual delta is that the swap from fallback to Fira Code happens about 130 ms earlier on a cold load, and a still image cannot show that.
What I did verify visually, on this branch in the running app, at 1280x800 and at 390x844: the shell renders in Fira Code exactly as before, the sidebar, header, empty-chat state and composer are unchanged, and
agent-browser errorswas empty throughout. I have both screenshots and will drop them into this thread if you would rather have the picture on the record.Not verified
/static/sw.js(index.html:2558), which scopes it to/static/, andnavigator.serviceWorker.controllerstayednullacross repeated loads of/. So the preload path with the SW intercepting is untested. Unrelated to this change, but it may be worth its own issue.rel="preload"withas="font"is unremarkable in both.