Repository navigation
The light themes' Warning and Info status colors read against their own page, and text on a status fill gets a measured ink in every theme (#3609) - #3627
Conversation
…wn page, and text on a status fill gets a measured ink in every theme (#3609) Light shipped WarningColor #F57F17 at 2.47:1 and InfoColor #2eaef1 (the accent itself) at 2.32:1 against BackgroundColor; Cool Breeze's WarningColor was 2.09:1. All three sit under the 3:1 non-text floor the new Settings contrast readout (#3606) paints red, and WarningBrush is TEXT in the viewer (fleet counts, seat state, coverage note), so the bar was 4.5:1, not 3:1. - Light WarningColor -> #AE4F08 (4.99 page / 5.35 card / 4.77 well / 4.72 alt row), InfoColor -> #0369A1 (5.53 / 5.93 / 5.29 / 5.23; same 201-degree hue as the accent, decoupled from it). Cool Breeze WarningColor -> #9E4A0B (4.82 / 5.51; 4.41 / 4.08 where it is only a marker). Dark untouched. - No amber clears 4.5:1 on Light's page AND under the #1A1A1A the Recommendations badge painted on it (the band is empty), so the ink is a theme key, StatusForegroundBrush, the way #3589 gave the accent AccentForegroundColor: white on the light themes, #111217 on Dark. A brush with a literal, not a nineteenth <Color>, so the eighteen-color palette pins in ThemeColorOverrideTests stand. The Recommendations badge (Lite, viewer, Dashboard) and the viewer's alert / attention badges take it; the latter were hard-coded White, 1.41:1 on Dark's amber. - Values and the ink key mirrored to the Darling viewer and the deprecated Dashboard so ThemeParityLiteDarlingTests and the Dashboard ThemeParityTests hold (70 and 67 shared keys, 0 mismatches). - Lite.Tests/ThemeStatusContrastTests pins every theme's four status colors at 3:1 on both page surfaces, Warning/Info at 4.5:1, the ink at 4.5:1 on every status fill, and the three consumers on the key. Text parse + WcagContrast, no WPF object; executed locally, fails on dev.
There was a problem hiding this comment.
LGTM — reviewed the theme XAML/C# diff (WPF, not T-SQL, so the collector-query style rules don't apply here). Spot-checked the WCAG math independently (hand-computed relative luminance for #F57F17/#AE4F08 against the Light page background: got 2.47:1 and 4.99:1, matching the PR's claimed before/after exactly). Verified: the new StatusForegroundBrush key is a literal SolidColorBrush (not a 19th <Color>) so ThemeColorOverrideTests' 18-color pin still holds; ThemeParityLiteDarlingTests/ThemeCompletenessTests/Dashboard's ThemeParityTests all compare key sets dynamically (no hardcoded counts to bump); Lite, Darling viewer, and deprecated Dashboard all got matching WarningColor/InfoColor/StatusForegroundBrush updates plus consistent Foreground swaps on their Recommendations severity badges. Darling's alert/attention badges correctly swap both the literal Brushes.White/Foreground="White" for DynamicResource StatusForegroundBrush alongside their existing WarningBrush/ErrorBrush DynamicResource fills. No correctness, parity, or security issues found.
Closes the palette half of what #3606 exposed: on a stock Light install the new Settings contrast readout paints two of its own status rows red, because
WarningColorandInfoColornever cleared the page they sit on. Everything here is measured with theWcagContrast#3606 checked in (WCAG 2.x relative luminance), not eyeballed; no screenshots are possible from this lane.The defect, measured
The readout measures each status color against
BackgroundColorand bands it green ≥ 4.5 / amber ≥ 3 / red < 3. Stock, before this PR:BackgroundLightColor)WarningColor#F57F17InfoColor#2eaef1(=AccentColor)WarningColor#F57F17(The issue text has Light's and Cool Breeze's
WarningColorfigures swapped — Light is 2.47, Cool Breeze 2.09. Both are under the floor; the conclusion is unchanged.)3:1 is the non-text floor, and it is not the bar these colors actually need. I traced every consumer of
WarningBrush/InfoBrushin Lite, the viewer andPerformanceMonitor.Ui:WarningBrushas text for the overview attention count, the fleet unknown-status line, the deadlock-coverage note, the seat state, and the 22-pt warning count;InfoBrushis the "■ Blue — Info" legend run in both Settings windows.#1A1A1Aink; the viewer's tab alert badge and server-list attention badge with a hard-codedWhiteink.So the fills had to reach 4.5:1 on the page and the card — and that is where the palette's structural problem shows.
Values
BackgroundDark)WarningColor#F57F17→#AE4F08InfoColor#2eaef1→#0369A1WarningColor#F57F17→#9E4A0BAccentForegroundColor, 6.79:1 — Text on an accent fill gets a measured ink in every theme, and the FinOps row marks become theme brushes Dark can actually see (#3577, bug arm) #3589), but a fill's value is the wrong value for a marker or a text tone. Decoupled to the same 201° hue, deep enough to read;AccentColoritself is untouched, so nothing about the selected tab, grid selection or accent buttons changes.Each value carries its before → after beside the
<Color>in XAML, tagged #3609, in the #3589 convention.The structural problem, and why this is not a value pick
Making a status color dark enough to read against the page makes it too dark for the dark text that used to sit on it. The band is empty: 4.5:1 on Light's page needs a relative luminance ≤ 0.17; 4.5:1 under
#1A1A1Aneeds ≥ 0.22. The old#1A1A1Aink measures 3.25:1 on the new Light amber and 2.93:1 on the new Info — and it was already 3.10:1 on the Critical badge's#C62828, a failing pair the bright amber had been hiding in plain sight.The resolution is the one #3589 gave the accent: the fill is tuned for the page and the ink becomes a theme key.
StatusForegroundBrush— the ink for text and glyphs on any status fill:#FFFFFF#FFFFFF(already its accent ink)#111217(the darkest surface tone, = its accent ink)It is a
SolidColorBrushwith a literal color (theWarningTextBrush/ApexBorderBrushidiom), not a nineteenth<Color>:ThemeColorOverrideTestspins the palette at exactly eighteen colors in every file, and this is ink that should follow the fills it sits on, not a slot the operator tunes independently. It is not exposed in the Settings Colors section for the same reason.Consumers moved onto the key:
Lite/Controls/RecommendationsTab.xamland the viewer's copy of that template inMainWindow.xaml— the two#1A1A1Ainks on the severity badge. Visible change on Light / Cool Breeze: the badge goes from dark text on a bright amber / cyan to white text on a deep amber / blue. On Dark the ink moves#1A1A1A→#111217, imperceptible.MainWindow.AlertBadges.cs,Brushes.White→SetResourceReference) and server-list attention badge (MainWindow.xaml,Foreground="White"). These are load-bearing edits outside the theme files, and they fix a pre-existing defect of the same class that the key makes free: White on Dark's#FFD54Famber is 1.41:1 — an alert count you could not read. Leaving two badge sites on a literal White while the third used the key would have put two inks on the same fill in one theme, which is what Text on an accent fill gets a measured ink in every theme, and the FinOps row marks become theme brushes Dark can actually see (#3577, bug arm) #3589 refused to do for the accent.Parity ports
ThemeParityLiteDarlingTests: 70 shared keys per theme (69 before — the new brush), 0 mismatches;ThemeCompletenessTestskey sets equal (95 Lite / 103 Darling ×3); every theme token referenced by either app's XAML resolves in all three themes.ThemeParityTestspins palette values Dashboard↔Lite — 67 shared, 0 mismatches — andPerformanceMonitor.Uichanges trip the path filter that runs it), plus the ink key and itsRecommendationsContent.xamlbadge, because a value-only mirror would have knowingly dropped that badge's ink to 3.25:1. Smallest faithful diff, its idiom (short parity comments).What the readout shows now (stock palette, computed the way
ThemeColorsPanel.RefreshReadoutscomputes it)Light and Dark read all-green. Cool Breeze still shows three amber rows — Success 4.04, Error 4.43, Info 4.25 (its Info is its accent,
#1E6FA8). Those are above the 3:1 floor the issue was opened for and were not among the values it named; they are the same design call on three more colors (and Info↔Accent coupling on Cool Breeze is the Light decision again), so they are reported below rather than folded in.What this PR does not do
AccentColoron any theme,WarningTextBrush, the row-mark brushes, or any control template.ThemeColorOverrides.Slots; the eighteen-color palette and the twelve exposed slots are as The operator can maintain the twelve palette colors of each theme from Settings, with a measured contrast readout and a reset to default (#3577, arm B) #3606 left them.CHANGELOG.mdis untouched; entry handed to the coordinator.Tests
Lite.Tests/ThemeStatusContrastTests(four facts, 21 cases): every theme's four status colors ≥ 3:1 on both page surfaces (the readout's own red line, pinned for all four so the defect cannot return through a different color);WarningColor/InfoColor≥ 4.5:1 on both surfaces (they are text — Cool Breeze's Info is exempted in a spelled-out line with its 4.25 figure, so the pin tightens the day it is fixed);StatusForegroundBrush≥ 4.5:1 on every status fill in every theme; and the three consumers spell the key with no literal#1A1A1A/Whiteleft. Text parse viaThemeXamlRewriter.DeclaredColors+WcagContrast— the same parser and arithmetic as the readout, no WPF object constructed. Executed locally on macOS through a net10.0 harness (realWcagContrast.cs; a stubColorstruct): 21/21 green on this branch, and pointed at pristinedevit fails with exactly the issue's numbers (2.47 / 2.32 / 2.09, plus the missing key).ThemeCompletenessTests(both apps),ThemeParityLiteDarlingTests, DashboardThemeParityTests, and the{StaticResource}hygiene check were replicated line-for-line in Python against the working tree: all green (figures above). CI runs the real ones.ThemeColorOverrideTests' eighteen-<Color>pins are unaffected by construction (no<Color>added).dotnet build -c Release -p:EnableWindowsTargeting=trueon Lite, Lite.Tests, the viewer, Darling.Tests, Dashboard and Dashboard.Tests: 0 warnings, 0 errors each; BAML regenerated with the new key and values.Out-of-lane findings (reported, not fixed)
#1B5E20(Material green-900) 6.1:1; Error#B71C1C(red-900) 5.3:1; Info would need decoupling fromAccentColoras Light's did. A palette call for the maintainer.ApexBorderBrush#1976D2is 4.29:1 on the Light page — fine as the border it is (3:1), noted only because Light's Info now sits near it in hue and reads deeper.SuccessBrush/InfoBrushas bold text; on Cool Breeze that is the 4.04 / 4.25 above.Closes #3609