gp-sphinx: Prevent flash of wrong theme on initial load#23
Merged
Conversation
why: When localStorage.theme and OS prefers-color-scheme disagree (e.g., user toggled to dark on a light-mode OS), the page briefly paints the OS-default scheme before settling on the stored choice. Furo's inline body-script runs after <body> opens, and the meta color-scheme tag defers canvas color to OS, so the wrong scheme can leak into first paint on slower networks/CPUs. what: - Add _inject_fowt_prevention() html-page-context callback that injects a synchronous <head> snippet via metatags - Snippet sets html.style.colorScheme to the resolved theme (canvas paints correctly) and adds a gp-sphinx-theme-pending gating class - New CSS rule hides body until Furo's body-script sets data-theme, so any pre-script body paint is invisible - DOMContentLoaded failsafe sets body[data-theme] from the head-resolved value if Furo's body-script ever stops running
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #23 +/- ##
==========================================
- Coverage 90.20% 90.19% -0.02%
==========================================
Files 163 163
Lines 13876 13880 +4
==========================================
+ Hits 12517 12519 +2
- Misses 1359 1361 +2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
tony
added a commit
that referenced
this pull request
Apr 26, 2026
why: Allow visual verification of the flash-of-wrong-theme fix at gp-sphinx.git-pull.com before merging PR #23. The live deploy uses the same prod S3 bucket + CloudFront distribution as main, so this will temporarily replace production docs. what: - Add tn-color-schemes to docs workflow push-trigger branches - TEMPORARY: revert this commit (or drop the line) before merging the FOWT fix to main
why: Cloudflare Rocket Loader rewrites every inline <script> to a private MIME type and runs it asynchronously after page load. On gp-sphinx.git-pull.com the FOWT-prevention head-script was being deferred past first paint — gating class never applied, body visibility:hidden never matched, and the flicker remained visible. Furo's own inline body-script suffers the same fate, so we can't rely on it to set body[data-theme] synchronously either. what: - Add data-cfasync="false" to the FOWT <script> so Rocket Loader leaves it alone — script now runs synchronously as written - Replace the single DOMContentLoaded body[data-theme] setter with a requestAnimationFrame + DOMContentLoaded pair, so we set the attribute as soon as document.body exists rather than waiting for Furo's now-async body-script - Update docstring to document the Rocket Loader bypass
why: _inject_copybutton_bridge sets window.GP_SPHINX_COPYBUTTON_SELECTOR in an inline <script> that spa-nav.js reads when re-creating copy buttons after SPA swaps. Without data-cfasync="false" the script is deferred by Cloudflare Rocket Loader, so the global is undefined when spa-nav.js runs and the override falls back to the default "div.highlight pre" — projects that customize copybutton_selector silently lose copy buttons on prompt admonitions etc. what: - Add data-cfasync="false" to the bridge <script> tag, matching the same Rocket Loader bypass already in _inject_fowt_prevention
…flicker
why: Cloudflare Rocket Loader defers external scripts too — furo.js
runs well after page load, and Furo's CSS rule
".no-js .theme-toggle-container {display: none}" keeps the
light/dark/auto toggle invisible until furo.js strips the no-js
class on DCL. The toggle then appears suddenly mid-load, which
reads as a flicker.
what:
- In _inject_fowt_prevention's head-script (already opted out of
Rocket Loader via data-cfasync="false"), strip the no-js class
from <html> right after adding gp-sphinx-theme-pending — the
toggle is now visible from first paint
- No-JS users still get the "no-js" class because the head-script
itself never runs for them, so the toggle stays correctly hidden
- Update docstring to document the behaviour
why: Each rule had `transition: background <duration>` for hover
smoothing. CSS has no per-trigger transition control, so the same
animation runs when the theme-toggle flips a CSS variable's value —
producing a visible mid-blend (e.g., grey → blue lerp) on every
dark/light swap. Snap on hover is acceptable; snap on theme swap is
the goal.
what:
- Delete `transition: background 100ms ease-out` from
api_style.css (dl.py headers, both top-level and nested) and
layout.css (rST/directive API headers and card-shell headers)
- Add `.sig:not(.sig-inline) { transition: none }` to override
Furo's compiled `transition: background .1s ease-out` on the same
selector — added in sphinx-gp-theme's theme custom.css for
downstream consumers AND in docs/_static/css/custom.css because
the project file shadows the theme file at the same path
tony
added a commit
to tmux-python/libtmux
that referenced
this pull request
Apr 27, 2026
why: gp-sphinx cut 0.0.1a11 covering a theme-flicker fix that addresses three independent failure modes on Furo + Cloudflare deploys. (1) Flash of the wrong colour scheme on initial load when localStorage.theme and OS prefers-color-scheme disagree. (2) Toggle-icon "pop-in" mid-load caused by furo.js being deferred by Cloudflare Rocket Loader, leaving <html class="no-js"> on the page and the .theme-toggle-container hidden until furo.js eventually ran. (3) Animated mid-blend on .sig and dl.py headers during runtime theme toggle, from CSS transitions on background-on-hover that also ran on every CSS-variable swap. Tracking the new alpha picks all three fixes up on the next docs build at https://libtmux.git-pull.com/. See git-pull/gp-sphinx#23 for the source-side detail. what: - pyproject.toml: gp-sphinx, sphinx-autodoc-api-style, and sphinx-autodoc-pytest-fixtures all move 0.0.1a10 -> 0.0.1a11 in every dependency group that pins them (one row in [project.optional-dependencies], one row in [dependency-groups] per package). - uv.lock regenerated against the new pins; gp-sphinx workspace siblings co-resolved to 0.0.1a11.
tony
added a commit
to vcs-python/libvcs
that referenced
this pull request
Apr 27, 2026
why: gp-sphinx cut 0.0.1a11 covering a theme-flicker fix that addresses three independent failure modes on Furo + Cloudflare deploys. (1) Flash of the wrong colour scheme on initial load when localStorage.theme and OS prefers-color-scheme disagree. (2) Toggle-icon "pop-in" mid-load caused by furo.js being deferred by Cloudflare Rocket Loader, leaving <html class="no-js"> on the page and the .theme-toggle-container hidden until furo.js eventually ran. (3) Animated mid-blend on .sig and dl.py headers during runtime theme toggle, from CSS transitions on background-on-hover that also ran on every CSS-variable swap. Tracking the new alpha picks all three fixes up on the next docs build at https://libvcs.git-pull.com/. See git-pull/gp-sphinx#23 for the source-side detail. what: - pyproject.toml: gp-sphinx, sphinx-autodoc-api-style, and sphinx-autodoc-pytest-fixtures all move 0.0.1a10 -> 0.0.1a11 in every dependency group that pins them (one row in [project.optional-dependencies], one row in [dependency-groups] per package). - uv.lock regenerated against the new pins; gp-sphinx workspace siblings co-resolved to 0.0.1a11.
tony
added a commit
to vcs-python/vcspull
that referenced
this pull request
Apr 27, 2026
why: gp-sphinx cut 0.0.1a11 covering a theme-flicker fix that addresses three independent failure modes on Furo + Cloudflare deploys. (1) Flash of the wrong colour scheme on initial load when localStorage.theme and OS prefers-color-scheme disagree. (2) Toggle-icon "pop-in" mid-load caused by furo.js being deferred by Cloudflare Rocket Loader, leaving <html class="no-js"> on the page and the .theme-toggle-container hidden until furo.js eventually ran. (3) Animated mid-blend on .sig and dl.py headers during runtime theme toggle, from CSS transitions on background-on-hover that also ran on every CSS-variable swap. Tracking the new alpha picks all three fixes up on the next docs build at https://vcspull.git-pull.com/. See git-pull/gp-sphinx#23 for the source-side detail. what: - pyproject.toml: gp-sphinx, sphinx-autodoc-argparse, and sphinx-autodoc-api-style all move 0.0.1a10 -> 0.0.1a11 in every dependency group that pins them (one row in [project.optional-dependencies], one row in [dependency-groups] per package). - uv.lock regenerated against the new pins; gp-sphinx workspace siblings co-resolved to 0.0.1a11.
tony
added a commit
to tmux-python/tmuxp
that referenced
this pull request
Apr 27, 2026
why: gp-sphinx cut 0.0.1a11 covering a theme-flicker fix that addresses three independent failure modes on Furo + Cloudflare deploys. (1) Flash of the wrong colour scheme on initial load when localStorage.theme and OS prefers-color-scheme disagree. (2) Toggle-icon "pop-in" mid-load caused by furo.js being deferred by Cloudflare Rocket Loader, leaving <html class="no-js"> on the page and the .theme-toggle-container hidden until furo.js eventually ran. (3) Animated mid-blend on .sig and dl.py headers during runtime theme toggle, from CSS transitions on background-on-hover that also ran on every CSS-variable swap. Tracking the new alpha picks all three fixes up on the next docs build at https://tmuxp.git-pull.com/. See git-pull/gp-sphinx#23 for the source-side detail. what: - pyproject.toml: gp-sphinx, sphinx-autodoc-argparse, and sphinx-autodoc-api-style all move 0.0.1a10 -> 0.0.1a11 in every dependency group that pins them (one row in [project.optional-dependencies], one row in [dependency-groups] per package). - uv.lock regenerated against the new pins; gp-sphinx workspace siblings co-resolved to 0.0.1a11.
tony
added a commit
to git-pull/gp-libs
that referenced
this pull request
Apr 27, 2026
why: gp-sphinx cut 0.0.1a11 covering a theme-flicker fix that addresses three independent failure modes on Furo + Cloudflare deploys. (1) Flash of the wrong colour scheme on initial load when localStorage.theme and OS prefers-color-scheme disagree. (2) Toggle-icon "pop-in" mid-load caused by furo.js being deferred by Cloudflare Rocket Loader, leaving <html class="no-js"> on the page and the .theme-toggle-container hidden until furo.js eventually ran. (3) Animated mid-blend on .sig and dl.py headers during runtime theme toggle, from CSS transitions on background-on-hover that also ran on every CSS-variable swap. Tracking the new alpha picks all three fixes up on the next docs build at https://gp-libs.git-pull.com/. See git-pull/gp-sphinx#23 for the source-side detail. what: - pyproject.toml: gp-sphinx and sphinx-autodoc-api-style move 0.0.1a10 -> 0.0.1a11 in every dependency group that pins them (one row in [project.optional-dependencies], one row in [dependency-groups] per package). - uv.lock regenerated against the new pins; gp-sphinx workspace siblings co-resolved to 0.0.1a11.
tony
added a commit
to tony/django-docutils
that referenced
this pull request
Apr 27, 2026
why: gp-sphinx cut 0.0.1a11 covering a theme-flicker fix that addresses three independent failure modes on Furo + Cloudflare deploys. (1) Flash of the wrong colour scheme on initial load when localStorage.theme and OS prefers-color-scheme disagree. (2) Toggle-icon "pop-in" mid-load caused by furo.js being deferred by Cloudflare Rocket Loader, leaving <html class="no-js"> on the page and the .theme-toggle-container hidden until furo.js eventually ran. (3) Animated mid-blend on .sig and dl.py headers during runtime theme toggle, from CSS transitions on background-on-hover that also ran on every CSS-variable swap. Tracking the new alpha picks all three fixes up on the next docs build at https://django-docutils.git-pull.com/. See git-pull/gp-sphinx#23 for the source-side detail. what: - pyproject.toml: gp-sphinx and sphinx-autodoc-api-style move 0.0.1a10 -> 0.0.1a11 in every dependency group that pins them (one row in [project.optional-dependencies], one row in [dependency-groups] per package). - uv.lock regenerated against the new pins; gp-sphinx workspace siblings co-resolved to 0.0.1a11.
tony
added a commit
to cihai/cihai
that referenced
this pull request
Apr 27, 2026
why: gp-sphinx cut 0.0.1a11 covering a theme-flicker fix that addresses three independent failure modes on Furo + Cloudflare deploys. (1) Flash of the wrong colour scheme on initial load when localStorage.theme and OS prefers-color-scheme disagree. (2) Toggle-icon "pop-in" mid-load caused by furo.js being deferred by Cloudflare Rocket Loader, leaving <html class="no-js"> on the page and the .theme-toggle-container hidden until furo.js eventually ran. (3) Animated mid-blend on .sig and dl.py headers during runtime theme toggle, from CSS transitions on background-on-hover that also ran on every CSS-variable swap. Tracking the new alpha picks all three fixes up on the next docs build at https://cihai.git-pull.com/. See git-pull/gp-sphinx#23 for the source-side detail. what: - pyproject.toml: gp-sphinx and sphinx-autodoc-api-style move 0.0.1a10 -> 0.0.1a11 in every dependency group that pins them (one row in [project.optional-dependencies], one row in [dependency-groups] per package). - uv.lock regenerated against the new pins; gp-sphinx workspace siblings co-resolved to 0.0.1a11.
tony
added a commit
to cihai/cihai-cli
that referenced
this pull request
Apr 27, 2026
why: gp-sphinx cut 0.0.1a11 covering a theme-flicker fix that addresses three independent failure modes on Furo + Cloudflare deploys. (1) Flash of the wrong colour scheme on initial load when localStorage.theme and OS prefers-color-scheme disagree. (2) Toggle-icon "pop-in" mid-load caused by furo.js being deferred by Cloudflare Rocket Loader, leaving <html class="no-js"> on the page and the .theme-toggle-container hidden until furo.js eventually ran. (3) Animated mid-blend on .sig and dl.py headers during runtime theme toggle, from CSS transitions on background-on-hover that also ran on every CSS-variable swap. Tracking the new alpha picks all three fixes up on the next docs build at https://cihai-cli.git-pull.com/. See git-pull/gp-sphinx#23 for the source-side detail. what: - pyproject.toml: gp-sphinx, sphinx-autodoc-argparse, and sphinx-autodoc-api-style all move 0.0.1a10 -> 0.0.1a11 in every dependency group that pins them (one row in [project.optional-dependencies], one row in [dependency-groups] per package). - uv.lock regenerated against the new pins; gp-sphinx workspace siblings co-resolved to 0.0.1a11.
tony
added a commit
to cihai/unihan-db
that referenced
this pull request
Apr 27, 2026
why: gp-sphinx cut 0.0.1a11 covering a theme-flicker fix that addresses three independent failure modes on Furo + Cloudflare deploys. (1) Flash of the wrong colour scheme on initial load when localStorage.theme and OS prefers-color-scheme disagree. (2) Toggle-icon "pop-in" mid-load caused by furo.js being deferred by Cloudflare Rocket Loader, leaving <html class="no-js"> on the page and the .theme-toggle-container hidden until furo.js eventually ran. (3) Animated mid-blend on .sig and dl.py headers during runtime theme toggle, from CSS transitions on background-on-hover that also ran on every CSS-variable swap. Tracking the new alpha picks all three fixes up on the next docs build at https://unihan-db.git-pull.com/. See git-pull/gp-sphinx#23 for the source-side detail. what: - pyproject.toml: gp-sphinx and sphinx-autodoc-api-style move 0.0.1a10 -> 0.0.1a11 in every dependency group that pins them (one row in [project.optional-dependencies], one row in [dependency-groups] per package). - uv.lock regenerated against the new pins; gp-sphinx workspace siblings co-resolved to 0.0.1a11.
tony
added a commit
to cihai/unihan-etl
that referenced
this pull request
Apr 27, 2026
why: gp-sphinx cut 0.0.1a11 covering a theme-flicker fix that addresses three independent failure modes on Furo + Cloudflare deploys. (1) Flash of the wrong colour scheme on initial load when localStorage.theme and OS prefers-color-scheme disagree. (2) Toggle-icon "pop-in" mid-load caused by furo.js being deferred by Cloudflare Rocket Loader, leaving <html class="no-js"> on the page and the .theme-toggle-container hidden until furo.js eventually ran. (3) Animated mid-blend on .sig and dl.py headers during runtime theme toggle, from CSS transitions on background-on-hover that also ran on every CSS-variable swap. Tracking the new alpha picks all three fixes up on the next docs build at https://unihan-etl.git-pull.com/. See git-pull/gp-sphinx#23 for the source-side detail. what: - pyproject.toml: gp-sphinx, sphinx-autodoc-argparse, sphinx-autodoc-api-style, and sphinx-autodoc-pytest-fixtures all move 0.0.1a10 -> 0.0.1a11 in every dependency group that pins them (one row in [project.optional-dependencies], one row in [dependency-groups] per package). - uv.lock regenerated against the new pins; gp-sphinx workspace siblings co-resolved to 0.0.1a11.
tony
added a commit
to tmux-python/libtmux-mcp
that referenced
this pull request
Apr 27, 2026
why: gp-sphinx cut 0.0.1a11 covering a theme-flicker fix that addresses three independent failure modes on Furo + Cloudflare deploys. (1) Flash of the wrong colour scheme on initial load when localStorage.theme and OS prefers-color-scheme disagree. (2) Toggle-icon "pop-in" mid-load caused by furo.js being deferred by Cloudflare Rocket Loader, leaving <html class="no-js"> on the page and the .theme-toggle-container hidden until furo.js eventually ran. (3) Animated mid-blend on .sig and dl.py headers during runtime theme toggle, from CSS transitions on background-on-hover that also ran on every CSS-variable swap. Tracking the new alpha picks all three fixes up on the next docs build at https://libtmux-mcp.git-pull.com/. See git-pull/gp-sphinx#23 for the source-side detail. what: - pyproject.toml: gp-sphinx, sphinx-autodoc-api-style, and sphinx-autodoc-fastmcp all move 0.0.1a10 -> 0.0.1a11 in every dependency group that pins them (one row in [project.optional-dependencies], one row in [dependency-groups] per package). - uv.lock regenerated against the new pins; gp-sphinx workspace siblings co-resolved to 0.0.1a11.
tony
added a commit
to tony/django-slugify-processor
that referenced
this pull request
Apr 27, 2026
why: gp-sphinx cut 0.0.1a11 covering a theme-flicker fix that addresses three independent failure modes on Furo + Cloudflare deploys. (1) Flash of the wrong colour scheme on initial load when localStorage.theme and OS prefers-color-scheme disagree. (2) Toggle-icon "pop-in" mid-load caused by furo.js being deferred by Cloudflare Rocket Loader, leaving <html class="no-js"> on the page and the .theme-toggle-container hidden until furo.js eventually ran. (3) Animated mid-blend on .sig and dl.py headers during runtime theme toggle, from CSS transitions on background-on-hover that also ran on every CSS-variable swap. Tracking the new alpha picks all three fixes up on the next docs build at https://django-slugify-processor.git-pull.com/. See git-pull/gp-sphinx#23 for the source-side detail. what: - pyproject.toml: gp-sphinx and sphinx-autodoc-api-style move 0.0.1a10 -> 0.0.1a11 in every dependency group that pins them (one row in [project.optional-dependencies], one row in [dependency-groups] per package). - uv.lock regenerated against the new pins; gp-sphinx workspace siblings co-resolved to 0.0.1a11.
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
_inject_fowt_prevention()html-page-contextcallback that emits a synchronous<head>snippet via Furo'smetatagsslothtml.style.colorScheme(so the html canvas paints in the user's preferred scheme on first paint) and agp-sphinx-theme-pendinggating class on<html>body[data-theme]— any paint that lands in that window is invisible, while the html canvas is already correctly themedbody[data-theme]from the head-resolved value if Furo's body-script ever stops running, so a future Furo refactor can't leave body permanently hiddenEliminates the flicker that was visible when
localStorage.themedisagreed with OSprefers-color-scheme(e.g., user toggled to dark on a light-mode OS), or on slower networks/CPUs where Furo's body-script lost the race against first paint.The visibility-hide approach reads zero Furo internals, so Furo can ship CSS-variable changes without dragging gp-sphinx along — there's no mirror to keep in sync.
Test plan
uv run ruff check . --fix --show-fixes— all checks passeduv run ruff format .— 181 files left unchangeduv run mypy— Success, no issues found in 176 source filesuv run py.test --reruns 0 -vvv— 1226 passed, 3 skippedjust build-docs— build succeededdocs/_build/html/index.htmlcontains the snippet at line 14, before all stylesheet<link>tagslocalStorage=null, OS=light→ light bg from frame 1,html.style.colorScheme="light"localStorage="dark", OS=light(worst-case mismatch) → dark bgrgb(19,20,22)from frame 1,html.style.colorScheme="dark"localStorage="light", OS=light→ light bg,html.style.colorScheme="light"body[data-theme], no regressionauto → dark/light → autocorrectly via Furo's existing handler