What happens
Every page load throws two React errors — minified #418 (hydration mismatch) and #423 (error during hydration, recovering with a client render). Reproduced in WebKit against a clean make build-theme, serving the visual fixture.
Why it matters beyond console noise
The #423 recovery re-renders the tree, and app/root.tsx passes the critical CSS as head={<style dangerouslySetInnerHTML={...} />}. So React puts the <style> block back shortly after hydration — including in the FOUC control test, which strips it from the served HTML precisely to prove the guard is meaningful.
Sampling under the control's exact conditions (external stylesheets aborted, inline <style> stripped from the document):
| t after DOMContentLoaded |
critical <style> tags |
.simple-center-grid display |
sidebar right |
| 30 ms |
0 |
block |
1272 px |
| 138 ms |
0 |
block |
1272 px |
| 195 ms |
1 |
grid |
0 px |
firstPaintState() samples after waitUntil: "domcontentloaded" plus a waitForSelector, so it has roughly a 150 ms budget. On a loaded CI runner it can slip past that, and then all three control assertions flip at once — gridDisplay and bodyFont (both pre-existing) and sidebarOnScreen (added in #123). fouc-guard gates PRs, so this is a latent red-for-no-reason.
Suggested direction
Two things, probably in this order:
- The hydration failure itself — the root cause, and worth understanding on its own regardless of the test. Running an unminified React build locally will name the mismatching node.
- Make the control insensitive to it — sample at first paint via an init script rather than after
domcontentloaded, or bail early with a clear message if the critical <style> reappears, so a slow runner reports the real reason instead of three confusing assertion failures.
Turned up while reviewing #123. Not introduced there — main carries the same exposure through its own two control assertions, which fail first.
What happens
Every page load throws two React errors — minified
#418(hydration mismatch) and#423(error during hydration, recovering with a client render). Reproduced in WebKit against a cleanmake build-theme, serving the visual fixture.Why it matters beyond console noise
The
#423recovery re-renders the tree, andapp/root.tsxpasses the critical CSS ashead={<style dangerouslySetInnerHTML={...} />}. So React puts the<style>block back shortly after hydration — including in the FOUC control test, which strips it from the served HTML precisely to prove the guard is meaningful.Sampling under the control's exact conditions (external stylesheets aborted, inline
<style>stripped from the document):<style>tags.simple-center-griddisplayrightblockblockgridfirstPaintState()samples afterwaitUntil: "domcontentloaded"plus awaitForSelector, so it has roughly a 150 ms budget. On a loaded CI runner it can slip past that, and then all three control assertions flip at once —gridDisplayandbodyFont(both pre-existing) andsidebarOnScreen(added in #123).fouc-guardgates PRs, so this is a latent red-for-no-reason.Suggested direction
Two things, probably in this order:
domcontentloaded, or bail early with a clear message if the critical<style>reappears, so a slow runner reports the real reason instead of three confusing assertion failures.Turned up while reviewing #123. Not introduced there —
maincarries the same exposure through its own two control assertions, which fail first.