fix: eliminate dark-mode flash in MonochromeUI/NewBootstrap

Merged
BO
4mo ago
+2130-2066

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:

  1. @run-at document-start + @resource BootstrapCSS
    Bootstrap 5.3.3 CSS is now declared as a @resource (cached by the userscript manager on install) and a @require for its JS. An early synchronous IIFE runs before the first paint and:

    • reads UserScript-Setting-Theme from localStorage and sets data-bs-theme on <html> immediately
    • injects Bootstrap 5.3.3 CSS from the local cache (no network round-trip)
    • injects the correct skin CSS (MonochromeSkinCSS or NewBootstrapSkinCSS, plus AddAnimation/AddColorText if enabled)
    • hides the page with html { opacity: 0 !important } to prevent any CLS while the old stylesheets are still in the CSSOM
    • arms a MutationObserver to intercept old Bootstrap/theme <link> tags as they are inserted by the HTML parser
  2. IIFE-time cleanup (at DOMContentLoaded)
    The main async IIFE now waits for DOMContentLoaded at startup (needed since the script now runs at document-start). Immediately after that wait — before await fetch(loginpage.php) — it:

    • disconnects the MutationObserver
    • removes any old Bootstrap/theme <link> elements still in the DOM (catches anything the preload scanner fetched before the observer could intercept)
    • removes the opacity: 0 hide style, revealing the page in its correct final state
  3. Late 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:

  • MonochromeSkinCSS and NewBootstrapSkinCSS extracted to top-level const so they are shared between the early block and the rest of the script (no duplication)
  • The Style element 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 IIFE

Confirmation

  • I have read the contributor's guide
  • My code includes comments where the logic is non-obvious
  • I have tested my changes in a browser — Note: this was developed and iterated in a remote Claude Code session without direct browser access. The PR author confirmed behaviour at each iteration step. Final browser confirmation by a human reviewer is needed before merge.
  • I am willing to provide support for this contribution if needed
  • My contribution is compatible with GNU General Public License v3.0
  • No duplicate PRs exist for this change
  • This change provides value to the community (eliminates a jarring UX issue in dark mode)
  • I accept that this PR may be rejected
  • This is a freely given contribution
  • Tested in both new UI and classic UI — only the new UI (MonochromeUI + NewBootstrap) path was changed; the classic UI code path is untouched. When NewBootstrap is disabled the early block exits immediately and the script behaves identically to before.