fix(contrast): measure translucent colours as they render, and keep alpha in dark mode - #703
Merged
Merged
Conversation
…lpha in dark mode ContrastService read only #rgb and #rrggbb and dropped the alpha of rgba(), so an 8-digit hex was 'not a colour' and faint text was measured as opaque. It now reads #rgba, #rrggbbaa and the rgba() alpha, and the contrast audit, the shipped-set audit, the compliance report and the dark palette's brand check measure a translucent background over the page background and a translucent foreground over that. DarkPaletteService derived every dark value as an opaque hex and skipped 8-digit hex tokens. The dark channels still come from the opaque colour, and a light alpha below 1 is kept as #rrggbbaa. The generator version is now 3 and every css/tokens/dark file is regenerated with it. Fixes #696
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:30 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 translucent token colour lost its transparency in the generated dark palette, and the contrast audit measured it as opaque, so faint text could pass a check it fails as rendered.
The defect
ContrastService::parseColor()read only#rgband#rrggbband dropped the alpha ofrgba(). An 8-digit hex was "not a colour", so the dark palette skipped it and the audit marked it unevaluated. Anrgba()value was measured as its opaque colour, andDarkPaletteService::deriveColorToken()wrote every dark value as an opaque hex, so an overlay likergba(0, 0, 0, 0.5)became solid in dark mode.The tests that were red
ContrastServiceTest::testFaintTextFailsAfterBlend,testEightDigitHexIsEvaluated,testTranslucentBackgroundIsBlendedOverThePage,testEvaluateBlendsATranslucentCandidate,testParseColorWithAlphaReadsEveryFormDarkPaletteServiceTest::testTranslucentSurfaceKeepsAlpha,testEightDigitHexIsDarkenedAndKeepsAlphaThe fix
ContrastServicereads#rgba,#rrggbbaaand thergba()alpha (number or percentage) through a newparseColorWithAlpha().parseColor()keeps returning the opaque channels for callers that want a colour.measure()blends a translucent background over the page background (--nldesign-color-background, else white) and a translucent foreground over that, then takes the ratio.check(),evaluate(), the shipped-set audit, the compliance report and the dark palette's brand-primary check measure through it. Opaque colours measure exactly as before.DarkPaletteServicederives the dark channels from the opaque colour as before and keeps a light alpha below 1 as#rrggbbaa. The generator version moves from 2 to 3, and all 46css/tokens/dark/*.cssfiles are regenerated with the real generator (the equivalent ofocc nldesign:generate-dark-variants --force). Installations regenerate on upgrade through the existing repair step, because a v2 header is no longer fresh.What the regeneration changes, beyond the header: translucent light values now keep their alpha in dark (for example the conduction-new footer text and focus ring), 8-digit hex tokens are now darkened instead of left out, and a few sets gain a token the v2 files did not carry (for example
--nldesign-component-heading-colorin conduction-new), which is what the current generator produces from unchanged sources.Not part of this change:
hsl()/hsla()are still not parsed (they stay unevaluated, as today), a foreground the dark repair loop rewrites is written opaque, and the token set converter's own ramp and guard helpers still read colours throughparseColor(). Blending a translucent primary before it reaches Nextcloud's theming is task 3.3 of the same change.Verified
composer check:strict: exit 0 (itstest:allskips in a bare clone, so PHPUnit ran separately).lib/privateon the autoloader: 868 tests, the same 11 errors and 7 failures as on development (missing Symfony and server classes outside a Nextcloud tree).DarkPaletteServiceTest::testRealShippedSetsPassContrastAfterGenerationpasses.npm run lint,format,test:l10n,check:l10n-js,check:manifest,test:unit: all exit 0.test:guest-css,test:component-scopes,test:lasuite-tokens: exit 0.--scope-to-diff: exit 0.Fixes #696