Runtime contrast for hand-written hex backgrounds
DESIGN/10's ≥ 4.5:1 rule gets its owner. ContrastMath is the pure seam: WCAG relative luminance (piecewise sRGB linearization), symmetric contrast ratio, source-over compositing (an #RRGGBBAA board colour resolves over the window background of the active appearance), and inkChoice — native label if it clears AA, else the other appearance's, else the higher ratio with meetsAA false (a mid-grey hex can max out below 4.5 against both 85%-alpha labels; the app paints the best available rather than overriding the user's colour). BoardTextInk is the board's application: the decision is a ColorScheme, not a Color — the text on the board fill is a hierarchy (.primary/.secondary/ .quaternary), and overriding the subtree's scheme moves the whole vocabulary coherently. Recomputed on appearance change by construction (read in body); label/backdrop colours resolve inside the asked-for appearance, Increase Contrast variants included. Two render sites — the only board text that sits on the user's colour: the lane header (lanes draw no plate; title, icon, badge, rename field and the + button land directly on the board fill) and the trash header (its wash is ~5% effective alpha). Menus, popovers, and drag replicas deliberately stay native; card faces carry their own opaque plates. The card's premise fell during implementation: 03's "palette pairs AA-verified at design time, pinned by a computed-contrast unit test" was false — no such test existed, and the m4 path drew the native label, failing AA in one appearance for all twelve wells (obsidian in Light Mode: 1.0:1). Palette names now route through the same ink selection (paintedColor delegates to Palette.nsColor — one predicate with BoardView's paint decision), and PaletteContrastTests pins that the chosen ink clears AA for every well in both appearances — plus theNativeLabelIsNeverEnough, which would have failed on the m4 code. Filed on the Redesign board for ratification. 1579 unit tests green, both schemes build. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -96,6 +96,10 @@ struct TrashLaneView: View {
|
||||
/// Styling), which is exactly the case the setting exists for (`Accommodations.Wash`).
|
||||
@Environment(\.accessibilityReduceTransparency) private var reduceTransparency
|
||||
|
||||
/// The window's appearance — 10-accessibility.md's runtime-contrast input, read in `body` so a
|
||||
/// light/dark flip recomputes the header's ink (`LaneView.colorScheme`'s twin, for its reason).
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
/// The live body metric — this column's geometry is `LaneView`'s, derived from the same font
|
||||
/// (`BoardMetrics`), because these are the same cards in a column that must read as their
|
||||
/// sibling.
|
||||
@@ -226,6 +230,25 @@ struct TrashLaneView: View {
|
||||
))
|
||||
}
|
||||
}
|
||||
// **The board's computed contrast ink** (10-accessibility.md; `BoardTextInk`) — the trash
|
||||
// header is the second and last place on the board where text sits on the user's colour,
|
||||
// whether that colour came from the palette grid or from a hand-written hex.
|
||||
//
|
||||
// The wash between the text and the board background does not save it: `.quaternary` at 50%
|
||||
// is a ~5%-alpha tint (`Accommodations.trashHeaderWash`), so the glyphs are effectively on
|
||||
// the board colour, and the decision is deliberately taken against that colour rather than
|
||||
// against a three-layer composite — modelling a tint that shifts the surface by a couple of
|
||||
// luminance points would add a whole layer of arithmetic to move no decision.
|
||||
//
|
||||
// **Outside the `.background`, unlike the lane's**, so the wash and the hatch flip with the
|
||||
// text: both are drawn in `.quaternary` — the same label vocabulary — and a hatch left in the
|
||||
// window's appearance over an inverted board is a dark texture on a dark surface, which
|
||||
// would quietly cost the trash the *non-colour* distinction 10-accessibility.md's
|
||||
// "state is never colour-alone" requires of it.
|
||||
.boardTextInk(BoardTextInk.scheme(
|
||||
forBoardBackground: store.snapshot.background,
|
||||
appearance: colorScheme
|
||||
))
|
||||
// The container carries the label and the count (see `body`), so the header itself is
|
||||
// decoration for VoiceOver rather than a second element saying the same thing.
|
||||
.accessibilityHidden(true)
|
||||
|
||||
Reference in New Issue
Block a user