darkreader / darkreader/darkreader
[Feature Request] Keep dynamic fixes active after dark theme detector stand-down ("theme off, fixes on")
- Dominant language
- TypeScript
- Stars
- 22.4k
- Forks
- 2.7k
- Avg merge
- 21h 11m
- Merged PRs (30d)
- 78
Description
### Prerequisites
- [x] I [searched for any existing report](https://github.com/darkreader/darkreader/issues?q=is%3Aissue) about this feature request to avoid opening a duplicate.
### Is this feature request related to a problem?
Yes
### Feature Request Description
**Problem**
Dark Reader's dark theme detector is strictly binary: when it concludes a site ships its own dark theme, it performs a full stand-down (`removeDynamicTheme()` + `CLEAN_UP`), discarding everything — including URL-matched dynamic fixes. That verdict is architecturally correct (a native dark theme should not be double-transformed), but it leaves a real class of sites unpatchable: **natively dark sites whose dark theme itself has a first-paint / hydration bug**. This pattern is common in SPAs with CSS-in-JS theme contexts that initialize after hydration. On such sites:
- No dynamic fix can help, because fixes are torn down together with the theme at the moment of stand-down (`src/inject/index.ts`, `ADD_DYNAMIC_THEME` handler). The fix database is structurally empty exactly where it is needed.
- The `NO DARK THEME` hint forces Dark Reader to keep transforming — rejected in #15959 as an anti-pattern, and rightly so.
- The `TARGET`/`MATCH` hint merged in #15959 cannot express it either (see below).
So today there is no mechanism that can say: *"trust this site's dark theme, but keep the documented fix entries to patch its broken first paint."*
**Concrete example: pixiv.net**
- An inline head script sets `` before first paint — correct.
- But the React theme context initially renders styled-components with light values and only syncs to dark after hydration. Captured with an `insertRule` recorder: light rule at ~1195 ms, dark variant of the same component at ~1352 ms.
- Out of the box (`detectDarkTheme` defaults to `true`), both the generic path (`runCheck()` checks `document.documentElement.dataset.theme === 'dark'` in `src/inject/detector.ts`) and the merged hint detect the site as dark and Dark Reader stands down entirely — leaving the white window exposed on every warm-start load.
- The merged `TARGET html` + `MATCH [data-theme="dark"]` hint is behaviorally a no-op here: it matches the same attribute on the same element the generic path already checks. Its only effect is timing — `detectUsingHint()` can fire before `canCheckForStyle()` would let `runCheck()` run, so the stand-down happens *earlier* and covers *less* of the race window. (Repro is timing-dependent: on MV3 cold start, service-worker wake-up can push the inject → detect cycle past the hydration window, which is likely why the flash did not reproduce during review.)
**Proposed solution**
A new opt-in directive in `detector-hints.config` — `KEEP FIXES`, usable alongside `TARGET`/`MATCH`:
- On dark detection for a hinted site, the content script drops the generated theme but keeps the URL's dynamic fix entries applied ("theme off, fixes on"), instead of the current `removeDynamicTheme()` + `DARK_THEME_DETECTED` → `CLEAN_UP` sequence.
- A fixes-only rebuild should skip the UA-stylesheet overrides an active theme injects (e.g. `html { background-color }` / `color-scheme`), since the native theme already handles those.
- Everything remains opt-in: sites without the directive behave exactly as today.
This directly resolves the anti-pattern concern from #15959 — Dark Reader no longer transforms native dark stylesheets, it only applies explicitly documented per-site patches — and it gives the fix database an actual role on native-dark sites. Without it, writing a per-site fix for pixiv is pointless, because no such fix can survive the stand-down.
**Design questions** (cc @alexanderby)
1. Is a hint directive the right shape, or would you prefer fixes to live in a separate stylesheet channel from the start, so stand-down removes only the theme channel?
2. Runtime theme flips (e.g. the user switches the site to light mode and `MATCH` stops matching): should detection re-run, and should the kept fixes be re-evaluated or removed?
3. After `DARK_THEME_DETECTED`, the background currently treats the tab like a dark-listed site (`isURLEnabled()`). Should the fixes-only state persist across navigations in the same way?
4. Fix-rot: the 2020 pixiv entry targeted styled-components hash classes that no longer exist. Is `dynamic-theme-fixes` still the right home for such patches, or should they belong to static themes / a new category?
### Screenshots
- insertRule recording of the pixiv.net hydration race: light rule inserted at ~1195 ms, dark variant of the same component at ~1352 ms (attached).
- Console trace of the warm-start sequence: DR theme injected → detector matches `html[data-theme="dark"]` → theme removed → white components visible until hydration completes.
- Control: same load with the detector disabled (`detectDarkTheme` off) — no flash, Dark Reader's near-identity transform covers the window.
### Additional Context
- Background discussion: #15959 (pixiv.net detector hint; includes the hydration-race analysis and the review conversation with @Myshor, who suggested opening this issue: "Proposing a new engine behavior or config directive ... affects core Dark Reader architecture beyond just pixiv.net").
**Related issues** (searched before filing; no duplicate of this proposal found):
- Same code path, state lifecycle: #15509 (dark-detected / NO DARK THEME state not re-evaluated during in-site navigation), #15913 + #15990 (cold-start race leaving `DETECTOR_HINTS_INDEX` undefined — also why detector-related flashes reproduce inconsistently).
- Hint DSL extensions currently in flight: #15929, #15930 (`MATCH SYSTEM DARK/LIGHT`) — this proposal adds a *post-verdict* directive rather than another matching mode.
- Verdict-accuracy requests, different problem: #1327, #10792, #13160, #12238, #15033 — these concern *whether* a site is dark; this proposal concerns what Dark Reader retains *after* a correct verdict.
- Likely unattributed instances of the same symptom: #4180 (Google Search), #13823 (addons.mozilla.org), #3307 (git.sr.ht).
- Tested with a dev build of main (~4.9.130), Chrome XX (MV3), Windows 11. Repro: load https://pixiv.net with default settings on a warm start.
- Happy to implement the directive and tests if the direction looks acceptable.
Contributor guide
Research direction
Start with src/inject/detector.ts, the detector-hints.config format, and the ADD_DYNAMIC_THEME handler in src/inject/index.ts. Trace the current dark-detection stand-down and determine how an opt-in KEEP FIXES directive would preserve URL-matched fixes without the theme's UA-stylesheet overrides. Done means the directive is explicitly supported, existing sites retain current behavior, and the detector lifecycle is covered by tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100