fix: draw the completion toast with the themed status brushes - #2118
Merged
Conversation
The toast's container follows the theme through Surface2 while three greens on top of it were literals: the tick's Foreground, its badge Background and the outer BorderBrush, all #22C55E -- the DARK-mode value. Measured against each preset's real Surface2, the tick came out at 1.65:1 on soft-blossom and never better than 2.08:1 on any of the six light presets, against 7.66:1 on midnight-indigo where the value was chosen. WCAG asks 4.5:1 for text and 3:1 for a meaningful graphic; it cleared neither. Six light presets, not the four the issue listed -- lavender and soft-blossom were missing, and soft-blossom is worst. ThemeService already seeds Success per mode (#22C55E dark, #166534 light) and the comment beside those values names this failure. The toast now uses Success, SuccessBgSubtle and SuccessBorder. Guarded by NoViewPaintsItselfWithALiteralColour rather than by widening NoThemedFill_CarriesAHardcodedWhiteForeground, which reads App.xaml styles bound to Accent/Danger looking for literal White -- different file, fill and literal. The new rule is cheap because the view layer had only seven literals in total. The four that remain are exceptions with distinct reasons: ThemePopup's swatches exist to BE a colour, and the FocusRing's two strokes must NOT follow the theme, since no single themed colour survives every surface the ring lands on. That pair is allowed by exact value, so a third literal in App.xaml still fails -- and the guard is what found it, my own measurement had missed App.xaml. Closes #2106
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.
Closes #2106.
What was wrong
The completion toast's container follows the theme (
Background="{DynamicResource Surface2}") while three greens on top of it were fixed literals: the tick'sForeground="#22C55E", its badgeBackground="#2022C55E"and the outerBorderBrush="#4022C55E".#22C55Eis the dark-mode value.Measured against each preset's real
Surface2, read out ofThemeService's preset table rather than approximated:Success#F1F5F9#E0F2FE#BBF7D0#FDE68A#E9D5FF#FBCFE8Six light presets, not the four #2106 listed — I had missed lavender and soft-blossom, and soft-blossom is the worst of them. On midnight-indigo the same green measures 7.66:1, which is why it reads correctly in dark mode and was never noticed. WCAG asks 4.5:1 for text and 3:1 for a meaningful graphic; it cleared neither on any light preset.
ThemeServicealready solved this: it seedsSuccessper mode —#22C55Edark,#166534light — and the comment beside those values names this exact failure, "one crisp card beside two washed-out ones". The toast simply did not use it.The fix
Success,SuccessBgSubtleandSuccessBorder, all three of which the theme already carries per mode.Mockup at
ui-mockups/toast-success-contrast.html, generated fromThemeService's preset table rather than hand-approximated, showing today against proposed for one dark preset and all six light ones with the ratio on each. There is no design choice in this change — the themed brush for this exact purpose already exists and the numbers are one-sided — so it is implemented rather than held for a decision.The guard, and why not the existing one
#2106 suggested widening
NoThemedFill_CarriesAHardcodedWhiteForeground. That guard reads App.xaml styles whoseBackgroundbindsAccentorDanger, looking for the literalWhite. Different file, different fill, different literal — widening it to cover this would have meant rewriting it into something else.NoViewPaintsItselfWithALiteralColouris the general rule instead: no colour-bearing attribute in the shell or inViews/may take a literal hex. It is cheap because the view layer was already nearly clean — seven literals in total, three of them the toast's.The other four are exceptions with reasons, and the reasons are different in kind:
ThemePopup.xaml, four 24×24 swatches —x:Named Borders whose fill the code-behind replaces with the value being edited. The literal is a designer-time default, and the element's job is to be a colour.App.xaml,Stroke="#111111"andStroke="#FFFFFF"— theFocusRing's two stacked strokes. Theme-independence is the feature: the ring has to stay visible onPrimaryButton's accent fill,DangerButton's red, a raised grey and a card surface, and the accent itself falls to 1.00:1 against one of those. Allowed by exact value, not by file, so a third literal in App.xaml still fails.The
App.xamlpair was found by the guard, not by me — my measurement had globbed the shell,Views/andResources/and missedApp.xaml.Verification
Mutation proof, both files restored byte-for-byte:
Foreground="#22C55E"MainWindow.xamlApp.xamlThemePopup.xamlThe second and third confirm both exception lists are load-bearing rather than decoration. Baseline and post-restore green.
197 cases green across
ArchitectureTests,ThemeServiceTestsand the theme-contrast suites. Builds 0 errors / 0 warnings,dotnet format --verify-no-changesclean on both, version consistency csproj 1.76.15 = CHANGELOG 1.76.15 = SECURITY 1.76.x.Not verified here: how it looks on screen, which needs the app running. What is verified is that the three brushes resolve (the XAML builds), that they are the per-mode ones, and the ratios above computed from the values the theme will actually apply.