Skip to content

fix: pin "back to top" to opacity 0 in the critical CSS - #168

Merged
mmcky merged 2 commits into
mainfrom
fix/back-to-top-critical-css
Sep 5, 2026
Merged

fix: pin "back to top" to opacity 0 in the critical CSS#168
mmcky merged 2 commits into
mainfrom
fix/back-to-top-critical-css

Conversation

@mmcky

@mmcky mmcky commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Pins the "back to top" button to opacity: 0 in the critical CSS, on the same zero-specificity terms as the toggle's close icon from #144, and has the WebKit FOUC guard assert it.

Why a critical-CSS rule and not the useMounted gate from #141

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 then fades out when the sheet lands. #141 assumed that frame is the initial paint and withheld the transition until after mount. Measured in WebKit against a make build-theme bundle of main, that is not where the frame comes from: WebKit blocks first paint on the head stylesheets here, and even with app.css delayed by 400ms the first painted frame was already styled. The unstyled frame arrives about 200ms after DOMContentLoaded, when the React #423 hydration recovery (#126) re-renders the head and briefly drops the stylesheet. By then the component is mounted and its transition is live, so gating on mount changes nothing.

Per-frame opacity of .qe-back-to-top with the stylesheet delayed 400ms, three runs each:

Build Frames at opacity 1 Animating frames
main 24–26 17–18
main + #141's Outline.tsx change 24–26 17–18
main + this rule 0 0

The inline <style> is restored with the tree on that re-render, which is why a rule in it holds through the gap. Its opacity-100 utility outranks the :where() rule, so the button still shows once scrolled (checked: 0 at the top of the page, 1 after scrolling 600px).

Test

fouc.spec.ts gains a backToTopOpacity probe next to the close-icon one. The main test expects "0" (pinned by the inline rule); the control expects "1" (with the rule stripped the button paints, which is the state the transition would animate away from). Both guard against the element going missing first, so a rename fails as itself.

Verified the guard still guards: removing the rule from CRITICAL_CSS fails the main test on backToTopOpacity (Expected: "0" / Received: "1") while the control still passes.

npm run compile clean · test:unit all passing · test:fouc 2 passed.

Relates to #126 (root cause, stays open), #127 (closed by #144) and #141 (superseded).

🤖 Generated with Claude Code

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>
@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 01:56 UTC

@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  39.5 seconds
commit  d79b867

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.

🟢 Approval recommended

The change is a small, zero-specificity additive critical-CSS rule verified not to affect live state, backed by consistent, null-guarded FOUC guard assertions.

Pull request overview

This PR fixes the "back to top" button fading out of the page margin during static-build page loads. Rather than gating the transition on mount (the superseded #141 useMounted approach, which cannot cover the React #423 hydration-recovery frame that re-renders the head and drops the stylesheet after mount), it pins the button to opacity: 0 via a zero-specificity :where() rule in the inlined critical CSS — the same mechanism already used for the toggle's close icon. The WebKit FOUC guard is extended to assert the new behavior.

Changes:

  • Add :where(.qe-back-to-top){opacity:0} to CRITICAL_CSS in app/root.tsx, with explanatory comments.
  • Extend tests/visual/fouc.spec.ts with a backToTopOpacity probe: main test expects "0", control expects "1".
  • Document the fix in CHANGELOG.md under [Unreleased].
File summaries
File Description
app/root.tsx Adds the zero-specificity :where(.qe-back-to-top){opacity:0} critical-CSS rule and comments explaining the hydration-recovery rationale.
tests/visual/fouc.spec.ts Adds a backToTopOpacity probe and null-guarded assertions in both the main and control cases.
CHANGELOG.md Records the fix under [Unreleased]/Fixed, noting it supersedes the #141 approach.
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 merged commit 5b0ac1d into main Sep 5, 2026
4 checks passed
@mmcky
mmcky deleted the fix/back-to-top-critical-css branch September 5, 2026 01:56
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