fix(overrides): give brand colour overrides the dark scopes, so every dark user sees one colour - #705
Merged
rubenvdlinde merged 1 commit intoSep 28, 2026
Conversation
… dark user sees one colour Every override sat in one :root block with !important. Nextcloud declares a chosen dark theme's colours on body, which a :root value never reaches, so a user who picked Dark theme kept core's colour while a user on System default with a dark OS saw the override. custom-overrides.css now also carries the two dark scopes of the generated dark stylesheets for every brand-layer colour override, with the value DarkPaletteService derives (the light value when it is not a colour literal). Component tokens are left out: both dark users already agree on them, and a body copy would outrank the primary-lock layer. The override import and the config bundle import read the :root block alone, so the dark copies in an exported file do not overwrite the light values. Fixes #698
Contributor
Quality Report — ConductionNL/thematiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 107/107 | |||
| npm | ✅ | ✅ 2/2 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ✅ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it on any branch from the Actions tab, or locally with npx playwright test. |
||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-28 15:40 UTC
Download the full PDF report from the workflow artifacts.
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.
A colour an admin set in the token editor showed for users on "System default" with a dark OS, but not for a user who picked the dark theme, so two dark users saw different colours.
The defect
CustomOverridesServicewrote every override into one:root {}block with!important. For a user who chose a theme, Nextcloud serves that theme's variables scoped to[data-theme-dark]and puts that attribute onbody, sobodyand everything in it used core's value and never inherited the one on:root;!importantonly wins between declarations on the same element. For "System default", core serves its dark values on:rootinside a media query, where the override does win.The tests that were red
tests/Unit/Service/CustomOverridesServiceDarkScopesTest.php, with the realDarkPaletteService:testDarkScopesWrittenandtestAnUnparseableColourKeepsItsValueInBothDarkScopesfailed on development (no dark block at all).testLightValuesReadBack,testComponentTokensGetNoDarkCopyandtestAnExportedFileReadsBackItsLightValuesguard the rest.The fix
custom-overrides.cssnow also carries the two dark scopes the generated dark stylesheets use: theprefers-color-scheme: darkblock on the untouchedbody, andbody[data-theme-dark], body[data-themes*=dark]. Both get the same value, so both kinds of dark user see one colour, and a user who chose the light theme is not affected.DarkPaletteService::deriveDarkValue()that the generator itself now uses, so the editor and the generated stylesheets never disagree. A value that is not a colour literal keeps its light value in both scopes.:root.:rootblock alone, throughCssParserService::parseOverridesFile(), so the dark copies in an exported file do not overwrite the light values.ConfigBundleServiceTest::testRoundTripExportWipeImportRestoresIdenticalStatecaught exactly that while this was built.A behaviour change to know about: a dark user on "System default" used to see the light override value itself; now every dark user sees its derived dark value, as
authoring-token-value-typesrequires. The admin's own dark value per colour (the second "Dark" field anddarkOverrides, tasks 2.2 and 2.3 in full) is not part of this change; this writes the derived value.Verified
composer check:strict: exit 0 (itstest:allskips in a bare clone, so PHPUnit ran separately).lib/privateon the autoloader: 875 tests, the same 11 errors and 7 failures as on development (missing Symfony and server classes outside a Nextcloud tree).npm run lint,format,test:l10n,check:l10n-js,check:manifest,test:unit: all exit 0.--scope-to-diff: exit 0.Fixes #698