Skip to content

test(fouc): sample first paint from an init script, assert control preconditions - #169

Merged
mmcky merged 5 commits into
mainfrom
test/fouc-control-init-script
Sep 5, 2026
Merged

test(fouc): sample first paint from an init script, assert control preconditions#169
mmcky merged 5 commits into
mainfrom
test/fouc-control-init-script

Conversation

@mmcky

@mmcky mmcky commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Carries the test-hardening half of #141 onto the current fouc.spec.ts (post-#144, post-#168). No component changes.

The control is racy

Hydration fails on every page load (React #418/#423, #126), and the #423 recovery re-renders the tree, which puts the inlined critical <style> back. Re-measured on current main while reviewing #141: the block reappears 150–240ms after DOMContentLoaded, and at that moment .qe-toc-toggle__close also flips to hidden. The control strips that block from the served HTML precisely to prove the guard is meaningful, so when React restores it, every control assertion flips at once. The measurement ran from the test after domcontentloaded, leaving roughly a 150ms budget. fouc-guard has never gone red for this on main (25+ runs), so this is latent rather than observed, but it is a PR-gating job that would fail for reasons unrelated to the critical CSS.

What changes

  • The measurement now happens in an init script that fires in-page on DOMContentLoaded, before hydration is even scheduled. Chosen over "bail early" because a control that strips the inline block guarantees a hydration mismatch by construction, so no repair to React hydration fails on every page load (#418/#423), which makes the FOUC control test racy #126 could ever make a post-hydration sample safe here. The ordering is empirical rather than guaranteed (Remix v1's inline type="module" async entry has to resolve its imports before entry.client.tsx schedules hydrateRoot), but the earliest observed restoration is an order of magnitude later than the sample.
  • Both cases assert their own preconditions. The strip reports whether it actually matched anything, so a reshaped CRITICAL_CSS fails as "stale strip pattern" rather than as "expected block, received grid". Each case also asserts whether the inline block is present as sampled.
  • The probes from refactor: rebuild the contents drawer on the Popover API #144 (sidebarRendered, toggleCloseRendered) and fix: pin "back to top" to opacity 0 in the critical CSS #168 (backToTopOpacity) are carried into the sampler unchanged.

Verification

The guard still guards: removing the grid rule from the built CRITICAL_CSS fails the main test with Expected: "grid" / Received: "block" while the control still passes, so the hardening desensitises the test to hydration without desensitising it to regressions.

npm run compile clean · test:fouc 2 passed.

Stacked on #168: this branch contains #168's commit, since both touch the same region of fouc.spec.ts and the back-to-top assertion needs its rule. It targets main directly so CI runs (ci.yml only triggers for PRs based on main), which means the diff shows both commits until #168 merges and then shrinks to this one. Merge #168 first. Relates to #126 (step 1, the hydration failure itself, stays open there) and #141 (superseded).

🤖 Generated with Claude Code

mmcky and others added 2 commits September 5, 2026 09:59
BackToTop is hidden by opacity-0 and carries transition-opacity, so on any
frame where the stylesheet is absent it paints at full opacity and fades out
once the sheet lands. That frame is produced by the React #423 hydration
recovery (#126), which re-renders the head and briefly drops the stylesheet
after the component has mounted — so gating the transition on mount, as #141
tried, cannot prevent it. Measured in WebKit with the stylesheet delayed:
17-18 animating frames without this rule, none with it.

The FOUC guard asserts the rule: the main test expects opacity 0, the control
expects 1 with the inline block stripped.

Supersedes the Outline.tsx change in #141. Relates to #126.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…econditions

The React #423 hydration recovery restores the inlined critical <style>
150-240ms after DOMContentLoaded (#126). The control strips that block from
the served HTML, so a post-domcontentloaded sample can see it restored and
every control assertion flips at once. Sample in-page on DOMContentLoaded
instead, before hydration is scheduled, and have both cases assert their own
preconditions so a reshaped CRITICAL_CSS reports as a stale strip pattern.

Carries the test half of #141 onto the post-#144 probes. Relates to #126.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-05 02:12 UTC

@mmcky
mmcky changed the base branch from fix/back-to-top-critical-css to main September 5, 2026 00:03
@mmcky mmcky closed this Sep 5, 2026
@mmcky mmcky reopened this Sep 5, 2026
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

🎭 Visual regression results

passed  19 passed
skipped  5 skipped

Details

stats  24 tests across 1 suite
duration  40.5 seconds
commit  39f1ea9

Skipped tests

mobile-chrome › theme.spec.ts › QuantEcon theme — visual regression › without JavaScript › drawer-opens-without-javascript
mobile-chrome › theme.spec.ts › QuantEcon theme — visual regression › drawer-closes-when-search-opens
mobile-chrome › theme.spec.ts › QuantEcon theme — visual regression › launch-colab
mobile-chrome › theme.spec.ts › QuantEcon theme — visual regression › live-compute-toggle
mobile-chrome › theme.spec.ts › QuantEcon theme — visual regression › live-compute-toggle-absent-without-thebe

Copilot AI 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.

🔵 Needs a closer look

It changes the behavior of a PR-gating test whose correctness now hinges on an init-script-vs-hydration ordering the author explicitly describes as "empirical rather than guaranteed," a reliability judgment best confirmed by a human maintainer.

Pull request overview

This PR hardens the WebKit FOUC guard test (tests/visual/fouc.spec.ts) so it stops being susceptible to a latent race: React hydration fails on every page load (#126), and the #423 recovery re-render restores the inlined critical <style> ~150–240ms after DOMContentLoaded — which, in the control case that deliberately strips that block, would flip every assertion at once and turn the PR-gating fouc-guard job red for reasons unrelated to the critical CSS. It moves the measurement into an in-page init script that samples on DOMContentLoaded before hydration is scheduled, and adds precondition assertions so a reshaped CRITICAL_CSS fails as "stale strip pattern" rather than as a confusing layout assertion. No production/component code changes.

Changes:

  • firstPaintState now installs a page.addInitScript sampler that measures the DOM on DOMContentLoaded (parking the result on window.__qeFirstPaint), replacing the post-domcontentloaded page.evaluate measurement.
  • isolateInlineCss returns a strippedCritical probe, and both tests assert their own preconditions (strippedCritical, criticalInline) so failures name their real cause.
  • CHANGELOG entry added under ## [Unreleased] > ### Fixed documenting the desensitisation and linking #126/#141.
File summaries
File Description
tests/visual/fouc.spec.ts Replaces post-DOMContentLoaded sampling with an init-script sampler; adds strip/inline preconditions to both cases via a returned probe and a criticalInline field.
CHANGELOG.md Documents the FOUC-guard hardening under [Unreleased] > Fixed with links to the related issue/PR.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@mmcky

mmcky commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

FYI @DrDrij, since you have been working in this area: this hardens the WebKit FOUC guard so its control case samples the page from an init script on DOMContentLoaded, before React's hydration recovery (#126) restores the stripped critical <style>, and both cases now assert their own preconditions so a reshaped CRITICAL_CSS reports as a stale strip pattern rather than as confusing grid failures. It carries your sidebarRendered / toggleCloseRendered probes from #144 and the backToTopOpacity one from #168 unchanged. Worth a glance if you touch fouc.spec.ts next; no action needed.

@mmcky
mmcky merged commit 48a32e5 into main Sep 5, 2026
4 checks passed
@mmcky
mmcky deleted the test/fouc-control-init-script branch September 5, 2026 02:12
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.

2 participants