fix: eliminate dark-mode flash in MonochromeUI/NewBootstrap#992
Merged
Conversation
Bootstrap 5.3.3 CSS was previously fetched from CDN at document-idle, causing a flash of the old light-themed page before dark mode took effect. Changes: - Add @run-at document-start so an early shim can run before first paint - Add @resource BootstrapCSS + @require Bootstrap JS bundle so both are cached by the userscript manager (no CDN round-trip on page load) - Add @grant GM_getResourceText - Early synchronous IIFE: reads saved theme/settings from localStorage, sets data-bs-theme on <html> immediately, injects Bootstrap CSS and the MonochromeUI or default skin CSS before the browser paints anything, and uses a MutationObserver to drop the page's own old Bootstrap/theme link tags before they are ever fetched - Extract MonochromeSkinCSS and NewBootstrapSkinCSS to top-level consts so they are available both to the early block and to the late Style element (which still applies them at document-end for any dynamic page additions) - Remove Bootstrap CSS + JS from the runtime CDN resources array - Remove the now-redundant post-load link-removal loop, data-bs-theme assignment, and earlyStyle CSS-variable injection from the NewBootstrap block - Add DOMContentLoaded wait at the start of the main async IIFE so all existing DOM-dependent code continues to work correctly under document-start https://claude.ai/code/session_01B1RgyUvtsWWhS2hdNiUhZb
The early block now owns the MonochromeSkinCSS / NewBootstrapSkinCSS injection. The Style element created at document-end is kept (other code appends to it further down the IIFE), but it no longer re-sets the skin CSS that was already applied synchronously at document-start. https://claude.ai/code/session_01B1RgyUvtsWWhS2hdNiUhZb
If GM_getResourceText("BootstrapCSS") returns null (the @resource has
not been downloaded yet on first install or update), the early block was
still setting up the MutationObserver that blocked the page's original
Bootstrap CSS — leaving the page with no Bootstrap CSS at all.
Fix: bail out of the early block before the observer is set up when the
resource is unavailable, so the page's original stylesheets load as a
fallback (flash still happens, but the page renders correctly).
Also add a CDN fallback entry to the runtime resources array: on the
rare load where the @resource wasn't ready, Bootstrap 5.3.3 CSS is
fetched from CDN instead, maintaining the same behaviour as before this
PR. Subsequent page loads use the cached @resource and no CDN request.
https://claude.ai/code/session_01B1RgyUvtsWWhS2hdNiUhZb
The browser preload scanner starts fetching old Bootstrap/theme
stylesheets before the document-start MutationObserver can remove
the link elements. Once fetched, those stylesheets are applied and
override our early-injected Bootstrap 5 + skin CSS — causing the new
UI to render for a moment and then immediately revert.
Restoring the IIFE-time link removal loop fixes this: removing a
<link> from the DOM un-applies its stylesheet from the CSSOM
immediately, regardless of whether the preload scanner had already
fetched it. The two layers now work together:
1. MutationObserver (document-start): best-effort early block that
prevents old CSS from ever loading when the timing works out.
2. Link removal loop (document-idle): guaranteed cleanup that
removes any preload-scanner-fetched stylesheets after the DOM
is ready, eliminating the revert.
https://claude.ai/code/session_01B1RgyUvtsWWhS2hdNiUhZb
The visible flash was caused by old Bootstrap 3 CSS sitting in the CSSOM from the moment DOMContentLoaded fired until the IIFE's link removal loop ran — a window that includes a full loginpage.php network round-trip. Move the definitive link removal into the early block's DOMContentLoaded handler so it fires at the earliest possible moment after the DOM is ready, before any async work in the main IIFE. The observer is also disconnected at the same time. The IIFE-level link removal loop is kept as a belt-and-suspenders fallback for edge cases (e.g. early block disabled due to cache miss). https://claude.ai/code/session_01B1RgyUvtsWWhS2hdNiUhZb
No JavaScript approach can reliably prevent the browser preload scanner
from fetching and visibly applying old Bootstrap CSS before DOMContentLoaded.
Attempting to evict stylesheets at DOMContentLoaded still leaves a window
where old CSS is visible, causing CLS.
Instead, hide the page immediately at document-start with
html { opacity: 0 !important; }
so the user never sees the intermediate broken state. The page is revealed
in the DOMContentLoaded handler, after old stylesheet links have been
removed and the correct Bootstrap 5 + skin CSS is already in place.
A 3-second safety-net timeout ensures the page is always revealed even
if something unexpected prevents DOMContentLoaded from firing.
This is the standard FOUC prevention pattern. The hidden window is just
HTML parse time (~50-150 ms for a server-rendered page), after which the
user sees the correct final state with zero CLS.
The hide style is only injected when the @resource is available
(_earlyBootstrapInjected = true), so the fallback path (cache miss on
first install) continues to show the page immediately.
https://claude.ai/code/session_01B1RgyUvtsWWhS2hdNiUhZb
The DOMContentLoaded listener registered inside the early IIFE's sandbox context was not firing reliably, causing the opacity:0 FOUC prevention style to only be removed by the 3-second safety-net timeout — resulting in a mandatory 3 s blank page on every load. Fix: expose the FOUC style element and MutationObserver via top-level variables (_foucStyle, _earlyObs) and perform the reveal + link cleanup at the top of the main async IIFE, immediately after its DOMContentLoaded wait. The IIFE's own DOMContentLoaded mechanism is proven to work, so the reveal is now reliable. The early block still hides the page at document-start and arms the MutationObserver for best-effort link interception; the IIFE takes care of teardown and the final CSSOM cleanup at DOMContentLoaded time, before the loginpage.php fetch begins. https://claude.ai/code/session_01B1RgyUvtsWWhS2hdNiUhZb
Contributor
There was a problem hiding this comment.
Sorry @boomzero, your pull request is larger than the review limit of 150000 diff characters
Deploying xmoj-script-dev-channel with
|
| Latest commit: |
69a65f5
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://14cdc70c.xmoj-script-dev-channel.pages.dev |
| Branch Preview URL: | https://claude-elegant-volta-u94vc7.xmoj-script-dev-channel.pages.dev |
Member
Author
|
This, btw does not completely fix the issue, but greatly alleviates it |
Member
Author
|
Sorry PR description is trash |
Member
Author
|
But fixes page flashes in dark mode |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: da0075c70e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
When GM_getResourceText("BootstrapCSS") returns null (first install or
@resource not yet cached), the early block exits before injecting
MonochromeSkinCSS/NewBootstrapSkinCSS. The Style element in the IIFE
now injects the skin CSS (plus AddAnimation/AddColorText overrides)
whenever _earlyBootstrapInjected is false, so the skin is always
applied regardless of cache state.
https://claude.ai/code/session_01B1RgyUvtsWWhS2hdNiUhZb
Member
|
looks good on first inpulse, btw i will look at it in the weekends
At 2026-06-14 16:52:27, "Zhu Chenrui" ***@***.***> wrote:
@boomzero requested your review on: XMOJ-Script-dev/XMOJ-Script#992 fix: eliminate dark-mode flash in MonochromeUI/NewBootstrap.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!
You are receiving this because your review was requested.Message ID: ***@***.***>
|
Member
Author
|
It's better though, and sometimes doesn't flash |
PythonSmall-Q
approved these changes
Jun 19, 2026
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.
Closes #343
What does this PR aim to accomplish?
The MonochromeUI dark mode flashed a light-coloured page on every load. This happened because Bootstrap 5.3.3 was fetched from CDN at document-idle — the page painted with the old Bootstrap 3 light theme first, then snapped to dark once the CDN response arrived.
How does this PR accomplish the above?
Root cause: Bootstrap CSS was loaded asynchronously from CDN inside the main async IIFE, which runs after the page has already painted with the site's original Bootstrap 3 stylesheet.
Fix — three cooperating layers:
@run-at document-start+@resource BootstrapCSSBootstrap 5.3.3 CSS is now declared as a
@resource(cached by the userscript manager on install) and a@requirefor its JS. An early synchronous IIFE runs before the first paint and:UserScript-Setting-ThemefromlocalStorageand setsdata-bs-themeon<html>immediatelyMonochromeSkinCSSorNewBootstrapSkinCSS, plus AddAnimation/AddColorText if enabled)html { opacity: 0 !important }to prevent any CLS while the old stylesheets are still in the CSSOMMutationObserverto intercept old Bootstrap/theme<link>tags as they are inserted by the HTML parserIIFE-time cleanup (at
DOMContentLoaded)The main async IIFE now waits for
DOMContentLoadedat startup (needed since the script now runs atdocument-start). Immediately after that wait — beforeawait fetch(loginpage.php)— it:MutationObserver<link>elements still in the DOM (catches anything the preload scanner fetched before the observer could intercept)opacity: 0hide style, revealing the page in its correct final stateLate cleanup fallback
The original link-removal loop inside
if (UtilityEnabled("NewBootstrap"))is kept as a belt-and-suspenders pass for edge cases.First-install / cache-miss graceful degradation:
If
GM_getResourceText("BootstrapCSS")returns null (resource not yet cached on first install), the early block exits without hiding the page or blocking stylesheets. The IIFE falls back to adding Bootstrap 5.3.3 CSS via CDN link (same as the old behaviour). After the first page load the resource is cached and all subsequent loads use the embedded path.Other changes:
MonochromeSkinCSSandNewBootstrapSkinCSSextracted to top-levelconstso they are shared between the early block and the rest of the script (no duplication)Styleelement created at document-idle no longer re-injects the skin CSS (already done early); it is kept for the dynamic CSS additions appended later in the IIFEConfirmation
NewBootstrapis disabled the early block exits immediately and the script behaves identically to before.